[철학과 개발] #20 Navigation - 대세를 따르라

https://steemitimages.com/640x0/https://ipfs.busy.org/ipfs/QmZLibU2cxuAstLRGr4RQA8KuptYUX875t5uFKb2SA798Q

P & D

Research and Development가 아니라 Philosophy and Development의 P & D 입니다. 개발글에 있어서 새로운 시도를 하고 있습니다. 그저 개발기를 적어 내려가는 것이 아니라, 어떤 철학을 가지고 개발을 해나가고 있는지, 어떤 시도를 했는지, 개발은 어떻게 하는지를 적어 내려갑니다. 이번 시리즈는 도움을 주고 받는 방법 - 이타인클럽의 철학과 개발 과정을 다룰 것입니다.

이전글 - [철학과 개발] #19 도와주고 싶을 때 도와준다!


철학

네이게이션 관련한 철학은 대세를 따르라입니다. React Native에서는 공식 네이게이션 프레임워크가 없습니다.
따라서 적절한 네이게이션 패키지를 선택해야 합니다.
여러 네비게이션 중에서 가장 많이 사용하는 React Navigation을 사용합니다.
다른 것도 사용해 봤지만, 사용법도 꽤 편합니다.
사용자도 많기 때문에 문제 상황이 닥쳤을 때 해결책도 찾기 쉽고, 문의하기도 쉽습니다.
그렇다고 해서 구현 관련해서 기능적인 제약도 없습니다.

대세를 따르면 됩니다!

개발

앱에서는 Bottom Tab Navigation을 사용하고 있습니다.

image.png

소스코드 전문은 다음을 참고하세요.
https://github.com/EtainClub/helpus

Navigator는 다음과 같이 구성되어 있습니다. Navigation을 React Navigation 4.x을 사용합니다.
React Navigation 4.x 부터 사용법이 조금 달라졌습니다. 다음과 같이 필요한 모듈을 별도로 import 해야 합니다.

// Navigator.js
import {createSwitchNavigator, createAppContainer} from 'react-navigation';
import {createBottomTabNavigator} from 'react-navigation-tabs';
import {createStackNavigator} from 'react-navigation-stack';

Bottom Tab Navigation은 4개의 흐름 (flow)를 갖습니다. 각각은 stack navigator를 사용하고, 전체를 switch navigator로 합니다.

도움 요청 flow

const askFlow = createStackNavigator(
  {
    AskMain: AskScreen,
    AskWait: AskWaitScreen,
  },
  {
    headerLayoutPreset: 'center',
  },
);
askFlow.navigationOptions = ({ screenProps: { t } }) => ({
  tabBarLabel: t('askTab'),
});

채팅 flow

const chatFlow = createStackNavigator(
  {
    ChatList: ChatListScreen,
    Chatting: ChatScreen,
  },
  {headerLayoutPreset: 'center'},
);
chatFlow.navigationOptions = ({ screenProps: { t } }) => ({
  tabBarLabel: t('chatTab'),
});

사용자 프로필 flow

const profileFlow = createStackNavigator(
  {
    Account: AccountScreen,
    AccountEdit: AccountEditScreen,
    ProfileContract: ProfileScreen,
    LocationVerify: LocationScreen,
    Users: UsersScreen,
    Leaders: LeadersScreen,
  },
  {headerLayoutPreset: 'center'},
);
profileFlow.navigationOptions = ({ screenProps: { t } }) => ({
  tabBarLabel: t('userTab'),
});

세팅 flow

const settingFlow = createStackNavigator(
  {
    Settings: SettingScreen,
    Language: LanguageScreen,
    LanguageAdd: LanguageAddScreen,
    NoticeList: NoticeListScreen,
    Notice: NoticeScreen,
  },
  {headerLayoutPreset: 'center'},
);
settingFlow.navigationOptions = ({ screenProps: { t } }) => ({
  tabBarLabel: t('settingTab'),
});

이들의 flow와 추가적인 로그인 flow 등을 합쳐서 switch navigator를 구성합니다.

const switchNavigator = createSwitchNavigator({
  ResolveAuth: ResolveAuthScreen,
  loginFlow: createStackNavigator({
    Intro: IntroScreen,
    Signin: SigninScreen,
  }),
  helpFlow: createStackNavigator(
    {
      Help: HelpScreen,
    },
    {headerLayoutPreset: 'center'},
  ),
  mainFlow: createBottomTabNavigator(
    {
      Ask: askFlow,
      Chat: chatFlow,
      Profile: profileFlow,
      Settings: settingFlow,
    },
    {
      defaultNavigationOptions: ({navigation}) => ({
        tabBarIcon: ({focused, horizontal, tintColor}) => {
          const {routeName} = navigation.state;
          if (routeName === 'Ask') {
            return <Icon2 name="hands-helping" size={20} color={tintColor} />;
          } else if (routeName === 'Chat') {
            return <Icon name="comments" size={20} color={tintColor} />;
          } else if (routeName === 'Profile') {
            return <Icon name="user" size={20} color={tintColor} />;
          } else if (routeName === 'Settings') {
            return <Icon name="gear" size={20} color={tintColor} />;
          }
        },
      }),
      tabBarOptions: {
        activeTintColor: '#fb8122',
        inactiveTintColor: 'gray',
        labelStyle: {
          fontSize: 15,
          margin: 0,
          padding: 0,
        },
      },
    },
  ),
});

마지막으로 다음과 같이 switch navigator를 export 합니다.

export default createAppContainer(switchNavigator);

네이게이션 팁

앱 개발을 하다 보니 최상위 파일인 App.js에서 네이게이션하는데 문제가 있었습니다. push notification을 App.js에서 받아서 도움 요청 스크린으로 네이게이션 해야 하는데, 이게 잘 안됐습니다. 특히 릴리스 모드에서요.
관련해서 stackoverflow에 질문을 올린 내용이 있습니다.
https://stackoverflow.com/questions/58068574/react-navigation-without-navigation-prop-does-not-work-in-release-mode-android

React Navigation의 공식 문서의 방법대로 그대로 따라하면 문제없이 구현됩니다.
https://reactnavigation.org/docs/4.x/navigating-without-navigation-prop/

위 방법은 navigation하기 위해 navigation prop이 없는 곳에서 navigation하기 위한 방법입니다.

이를 사용하기 위해서 최상위 파일인 App.js의 네이게이션 구조는 다음과 같습니다.

// App.js
import Navigator from './src/Navigator';
import NavigationService from './src/NavigationService';

export default () => {

  const AppContainer = Navigator;
  
  return (
    <ChatProvider>
      <ProfileProvider>
        <HelpProvider>
          <AskProvider>
            <AuthProvider>
              <AppContainer
                ref={navigationRef => {NavigationService.setTopLevelNavigator(navigationRef);}}
                screenProps={{ 
                  t: translation
                }}
              />
            </AuthProvider>
          </AskProvider>
        </HelpProvider>
      </ProfileProvider>
    </ChatProvider>
  );

위 코드를 보면 ref를 사용하여 네이게이션을 설정했습니다. NavigationService.js는 다음과 같습니다.

// NavigationService.js
import { NavigationActions } from 'react-navigation';

let _navigator;

function setTopLevelNavigator(navigatorRef) {
  if (navigatorRef) {
    _navigator = navigatorRef;
  } else {
    console.log('navigatorRef is null');
  }
}

function navigate(routeName, params) {
  _navigator.dispatch(
    NavigationActions.navigate({
      routeName,
      params,
    })
  );
}

export default {
  navigate,
  setTopLevelNavigator,
}

도움 주고 받는 앱 helpus

앱 사용자 중 다음과 같은 도움이 가능하 사용자들이 있습니다.

  • 최면 상담
  • 영어, 일본어 관련 문제
  • 건강 상담, 심리 상담

물론, 저도 명상, 루시드 드림 관련 도움을 줄 수 있습니다.

보다 자세한 내용은 홈페이지를 참고해 주세요.
https://etain.club