Navigate To RN Page From Player

Scenarios

Scenario 1

When users click the CTA, the default behavior is rendering the CTA link content using the native web view. But the host app developers may want to render the CTA link content using the React Native page.

Scenario 2

When users click the cart icon, the host app may want to navigate the custom React Native cart page.

Issues

Both the CTA button and cart icon belong to the video player. There are similar issues in these two scenarios. As follows, when the video player is launched, the RN navigation stack is covered by the video player.

Generally, the host app use React Navigation library to handle the page navigation in RN App. And when the host app developers use the push method of React Navigation to navigate to the RN page in the above scenarios, the page will be rendered in the bottom RN navigation stack. Therefore, the new RN page would be covered by the video player(native page).

Solutions

We provide two solutions to handle these scenarios:

  1. Start floating player

  2. Make the RN page overlaying the full-screen player

  3. Reordering RN container and native container

Start floating player

If the floating player is enabled, the host app could start the floating player when users click the CTA button or the cart icon.

FireworkSDK.getInstance().onCustomCTAClick = async (event) => {
  const result = await FireworkSDK.getInstance().navigator.startFloatingPlayer();
  if (!result) {
    /* when the result is false, the current fullscreen player may not
     * enable the floating player. In that case, we could call the
     * following method to close the fullscreen player.
     */
    await FireworkSDK.getInstance().navigator.popNativeContainer();
  }
  
  // Navigate to the RN webview page of the host app.
  navigation.navigate('LinkContent', { url: event.url });
}
FireworkSDK.getInstance().shopping.onCustomClickCartIcon = async () => {
  const result = await FireworkSDK.getInstance().navigator.startFloatingPlayer();
  if (!result) {
    /* when the result is false, the current fullscreen player may not
     * enable the floating player. In that case, we could call the
     * following method to close the fullscreen player.
     */
    await FireworkSDK.getInstance().navigator.popNativeContainer();
  }
  
  // Navigate to the RN cart page of the host app.
  navigation.navigate('Cart');
};

Make the RN page overlay the full-screen player

As shown below, we push a new RN container overlaying the full-screen player in this solution.

Create a new App component for the top RN container

import React, { useEffect, useMemo } from 'react';

import { BackHandler, Platform } from 'react-native';
import { ThemeProvider } from 'react-native-elements';
import FireworkSDK from 'react-native-firework-sdk';
import { RootSiblingParent } from 'react-native-root-siblings';
import { Provider } from 'react-redux';

import { ActionSheetProvider } from '@expo/react-native-action-sheet';
import {
  createNavigationContainerRef,
  NavigationContainer,
} from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import AppTheme from './AppTheme';
import BackButton from './components/BackButton';
import Cart from './screens/Cart';
import Checkout from './screens/Checkout';
import LinkContent from './screens/LinkContent';
import type { TopParamList } from './screens/paramList/TopParamList';
import { store } from './store';

const StackNavigator = createNativeStackNavigator<TopParamList>();

type AppRouteName = keyof TopParamList;
export interface IFWNavigationContainer {
  initialRouteName?: keyof TopParamList;
  initialParams?: any;
}
const FWTopNavigationContainer = ({
  initialRouteName,
  initialParams,
}: IFWNavigationContainer) => {
  console.log(
    'FWNavigationContainer initialRouteName',
    initialRouteName,
    'initialParams',
    initialParams
  );

  const navigationRef = useMemo(() => createNavigationContainerRef(), []);

  const renderScreen = ({
    name,
    options,
    component,
  }: {
    name: AppRouteName;
    options?: any;
    component: React.ComponentType<any>;
  }) => {
    const isInitialScreen = initialRouteName === name;
    return (
      <StackNavigator.Screen
        name={name}
        // We should pass initialParams to the initial screen
        initialParams={isInitialScreen ? initialParams : undefined}
        component={component}
        options={options}
      />
    );
  };

  useEffect(() => {
    if (Platform.OS === 'android') {
      const onBackPress = () => {
        console.log(
          'onBackPress current route is',
          navigationRef.getCurrentRoute()
        );
        if (navigationRef.getCurrentRoute()?.name === initialRouteName) {
          // It's the first screen.
          // We should call popRNContainer method to close
          // the RN Container overlaying the full-screen player.
          FireworkSDK.getInstance().navigator.popRNContainer();
          return true;
        }

        return false;
      };
      const subscription = BackHandler.addEventListener(
        'hardwareBackPress',
        onBackPress
      );
      return () => {
        subscription.remove();
      };
    }

    return;
  }, [initialRouteName, navigationRef]);

  return (
    <NavigationContainer ref={navigationRef}>
      <StackNavigator.Navigator
        initialRouteName={initialRouteName}
        screenOptions={({ navigation }) => ({
          headerTitleAlign: 'center',
          headerBackTitleVisible: false,
          headerBackButtonMenuEnabled: false,
          headerBackVisible: false,
          headerLeft: ({ tintColor, canGoBack }) => {
            return (
              <BackButton
                tintColor={tintColor}
                size={30}
                onBack={() => {
                  console.log('FWTopNavigationContainer canGoBack', canGoBack);
                  if (canGoBack) {
                    navigation.goBack();
                  } else {
                    // It's the first screen when canGoBack is false.
                    // We should call popRNContainer method to close
                    // the RN Container overlaying the full-screen player.
                    FireworkSDK.getInstance().navigator.popRNContainer();
                  }
                }}
              />
            );
          },
        })}
      >
        {renderScreen({
          name: 'Cart',
          component: Cart,
          options: { title: 'Host App RN Cart page' },
        })}
        {renderScreen({
          name: 'Checkout',
          component: Checkout,
          options: { title: 'Host App RN Checkout page' },
        })}
        {renderScreen({
          name: 'LinkContent',
          component: LinkContent,
          options: { title: 'Host App RN In-app browser' },
        })}
      </StackNavigator.Navigator>
    </NavigationContainer>
  );
};

export interface ITopAppProps {
  initialRouteName?: AppRouteName;
  initialParams?: any;
}
export default function TopApp(props: ITopAppProps) {
  return (
    <Provider store={store}>
      <ThemeProvider theme={AppTheme}>
        <ActionSheetProvider>
          <RootSiblingParent>
            <FWTopNavigationContainer {...props} />
          </RootSiblingParent>
        </ActionSheetProvider>
      </ThemeProvider>
    </Provider>
  );
}
  1. The top app component and the bottom app component share the same memory. You could use the redux store to share the global app state.

  2. In the first screen of the top RN container, you should call FireworkSDK.getInstance().navigator.popRNContainer() to close the top RN container when users click the header back button or Android hardware back button.

Register the new App component

AppRegistry.registerComponent(topAppName, () => TopApp);
FireworkSDK.getInstance().onCustomCTAClick = async (event) => {
  FireworkSDK.getInstance().navigator.pushRNContainer({
    appKey: topAppName,
    appProps: {
      initialRouteName: 'LinkContent',
      initialParams: { url: event.url },
    },
  });
}

You could also find the complete example in our sample app.

Reordering RN container and native container

Bring the bottom RN container to the front

We provide bringRNContainerToTop API for bringing the bottom RN container to the front. When you want to navigate from the native page to the RN page, you could do the navigation on the RN container and call our API to bring the bottom RN container to the front. The sample codes are:

FireworkSDK.getInstance().onCustomCTAClick = async (event) => {
  // Do the navigation on the RN container
  RootNavigation.navigate('LinkContent', {
    ...params,
    // Mark the page is navigated from Firework native page. We should
    // call bringRNContainerToBottom when leaving this page.
    isFromNativeNavigation: true,
  });
  // Bring the bottom RN container to the front
  await FireworkSDK.getInstance().navigator.bringRNContainerToTop();
}

Bring the top RN container to the bottom

For example, the user is navigated to page A when clicking the CTA button. As the RN container is on the top, you need to pop the page on the RN container and call bringRNContainerToBottom when leaving page A. The sample codes are:

const StackNavigator = createNativeStackNavigator<RootStackParamList>();
<StackNavigator.Navigator
  initialRouteName={'Tab'}
  screenOptions={({ route, navigation }) => ({
    headerTitleAlign: 'center',
    headerBackTitleVisible: false,
    headerBackButtonMenuEnabled: false,
    headerBackVisible: false,
    headerLeft: ({ tintColor, canGoBack }) => {
      return (
        <BackButton
          onBack={async () => {
            console.log('FWTopNavigationContainer canGoBack', canGoBack);
            if (canGoBack) {
              navigation.goBack();
            }
            // As the page is navigated from Firework native page, we
            // should call bringRNContainerToBottom when leaving this page.
            if ((route.params as any)?.isFromNativeNavigation) {
              FireworkSDK.getInstance().navigator.bringRNContainerToBottom();
            }
          }}
          tintColor={tintColor}
          size={30}
        />
      );
    },
  })}
>
  {renderScreen({
    name: 'Tab',
    component: Tab,
    options: { headerShown: false },
  })}
  {renderScreen({
    name: 'Feed',
    component: Feed,
  })}
  {renderScreen({
    name: 'Cart',
    component: Cart,
    options: {
      title: 'Host App Cart',
    },
  })}
  {renderScreen({
    name: 'LinkContent',
    component: LinkContent,
    options: { title: 'Link Content(RN page)' },
  })}
</StackNavigator.Navigator>

You could also find the complete example in our sample app.

Demo Video

The following is the demo video. As the react-navigation library does the navigation when the view is shown, you can see the push navigation animation in the RN container when navigating to the RN page from the player.

Last updated