Don't think! Just do it!

종합 IT 기술 정체성 카오스 블로그! 이... 이곳은 어디지?

Flutter/Flutter Study

React Native에서 Flutter로 갈아타기 #9 - Navigation 두번째

방피터 2022. 10. 11. 07:59

2022.10.10 - [Flutter] - React Native에서 Flutter로 갈아타기 #8 - Navigation

 

React Native에서 Flutter로 갈아타기 #8 - Navigation

React native에서는 react navigation를 사용했었는데.... react navigation 말고 다른 옵션이 있나? 음.. 암튼 React navigation에는 여러가지 navigator가 있는데 보통 Bottom tab navigator에 stack navigator..

engschool.tistory.com

👆👆 (이전 글에서) React navigation으로 했던 bottom tab navigator와 stack navigator를 합친 구조를 Flutter에서 구현해봤어. 네비게이터는 하나만 만들었었는데. 오늘은 3개 전부 다 만들어 볼거구. 스크린도 데이터를 받도록 변경해서 탭 이름을 나타내도록 할거야.

우선 ScreenA.dart를 변경해볼게. 심플해. int tabIndex를 받을 거고 그걸 화면에 뿌려줄거야.👇👇 

import 'package:flutter/material.dart';
import 'ScreenB.dart';

class ScreenA extends StatelessWidget {
  const ScreenA({super.key, required this.tabIndex});
  final int tabIndex;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("ScreenA")),
        body: Center(
          child: Column(children: [
            Text("from tab: ${tabIndex.toString()}"),
            TextButton(
              child: Text("Go to ScreenB"),
              onPressed: () {
                Navigator.pushNamed(context, '/ScreenB');
              },
            )
          ]),
        ));
  }
}

ScreenB.dart도 동일하게 했어. 그래서 코드는 생략. 그리고 tab 별로 3개의 navigator를 준비해주고 자신의 스크린에 tabIndex를 넘겨줄거야. 그 중 하나만 PersonNavigator.dart 만 보면👇👇 screen에 전달할 데이터 넣어준거 말고는 없어.

import 'package:flutter/material.dart';
import 'package:study_flutter/screens/ScreenA.dart';
import 'package:study_flutter/screens/ScreenB.dart';

class PersonNavigator extends StatelessWidget {
  const PersonNavigator({super.key, required this.tabIndex});
  final int tabIndex;
  Map<String, WidgetBuilder> _routeBuilder(BuildContext context) {
    return {
      "/": (context) => ScreenA(
            tabIndex: tabIndex,	//이거 말고는 변한게 없음!
          ),
      "/ScreenB": (context) => ScreenB(
            tabIndex: tabIndex,	//이거 말고는 변한게 없음!
          ),
    };
  }

  @override
  Widget build(BuildContext context) {
    final routeBuilder = _routeBuilder(context);
    return Navigator(
      initialRoute: '/',
      onGenerateRoute: ((settings) {
        return MaterialPageRoute(
          builder: (context) => routeBuilder[settings.name!]!(context),
        );
      }),
    );
  }
}

tab 별 navigator를 만들어주었다.

이제 main.dart만 수정해주면 끝! scaffold body에 PersonNavigator 위젯만 넣어줬었는데 이제는 tab별로 navigator를 넣어줘야 하잖아? 그 작업을 임시로 해봤어. 왜 "임시"인줄은 잠시 후 설명해줌.

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  int _currentTabIndex = 0;
  void _tabSelect(int tabIndex) {
    setState(() {
      _currentTabIndex = tabIndex;
    });
  }
  //여기부터
  List<Widget> _tabNavigatorBuilder() {
    return [
      PersonNavigator(tabIndex: _currentTabIndex),
      AlarmNavigator(tabIndex: _currentTabIndex),
      UmbrellaNavigator(tabIndex: _currentTabIndex),
    ];
  }
  //여기까지 추가
  
  @override
  Widget build(BuildContext context) {
    //여기부터
    final TabNavigationBuilder = _tabNavigatorBuilder();
    //여기까지
    return Scaffold(
      //여기부터
      body: Center(child: TabNavigationBuilder.elementAt(_currentTabIndex)),
      //여기까지
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.person), label: "Person"),
          BottomNavigationBarItem(icon: Icon(Icons.alarm), label: "alarm"),
          BottomNavigationBarItem(icon: Icon(Icons.umbrella), label: "umbrella")
        ],
        currentIndex: _currentTabIndex,
        onTap: (value) => _tabSelect(value),
      ),
    );
  }
}

 tabIndex는 사실 그냥 숫자야. 첫번째 탭은 0 그 다음은 1 그 다음은 2. 그래서 navigator list를 하나 만들고 elementAt(index) 함수를 이용해서 원하는 tabNavigator를 body에 보여주도록 했어.👆👆 이제 실행시켜보면 👇👇 각 tab마다 index 번호가 잘 보이는 걸 확인할 수 있어.🎉🎉

tab별 navigator가 잘 보인다. 어! 근데!

어.. 근데... 각 tab마다 navigator 상태가 유지가 안되네;;; flutter는 상태가 변경되면 자신에 포함된 자식 전체를 다시 그려버리기 때문에 다른 탭으로 넘가갔다가 돌아오면 네비게이션의 상태가 초기화되버려. 이건 우리가 원한 결과가 아니잖아~ 상태가 그대로 유지가 되어야지! 이게 바로 아까 elementAt(index)를 "임시"로 사용한 이유야.

나는 각 tab의 navigator의 상태를 유지하기 위한 state를 유지해야 하나? 라고 생각하고 있었는데;;; 이래 저래 찾아보니까;; 해법은 매우 심플! 요약하면 모든 네비게이션을 다 그려버리고 필요없는 건 안보이게 한다야 😅😅 👇

그냥 다 그려버리고 필요없는 걸 안보이게 하지. 무식한 방식이 맘에 든다!

이렇게 하기 위해서는 Stack 위젯과 OffStage라는 위젯을 사용하는데 Stack 위젯으로 모든 tab의 navigator를 겹치게 그리고 OffStage 위젯으로 각 navigator를 감싸서 현재 탭이 자기 것이 아니면 안보이도록 하는 방식이야. 음 왠지 무식하지만 심플한게 마음에 쏙드는 방식이군!! 설명한데로 main.dart의 scaffold body 부분을 Stack과 OffStage를 사용하도록 변경했어👇👇

class MyApp extends StatefulWidget {
  const MyApp({super.key});
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  int _currentTabIndex = 0;
  void _tabSelect(int tabIndex) {
    setState(() {
      _currentTabIndex = tabIndex;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //여기부터
      body: Stack(children: [
        Offstage(
          offstage: _currentTabIndex != 0,
          child: PersonNavigator(tabIndex: 0),
        ),
        Offstage(
          offstage: _currentTabIndex != 1,
          child: AlarmNavigator(tabIndex: 1),
        ),
        Offstage(
          offstage: _currentTabIndex != 2,
          child: UmbrellaNavigator(tabIndex: 2),
        ),
      ]),
      //여기까지 수정
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.person), label: "Person"),
          BottomNavigationBarItem(icon: Icon(Icons.alarm), label: "alarm"),
          BottomNavigationBarItem(icon: Icon(Icons.umbrella), label: "umbrella")
        ],
        currentIndex: _currentTabIndex,
        onTap: (value) => _tabSelect(value),
      ),
    );
  }
}

그리고 나서 테스트를 해보면 👇👇아래와 같이 Screen 상태가 유지되는 걸 확인할 수 있어!

tab별 Navigator 상태가 잘 유지된다.(사소한 건 신경쓰지 말자)

음 이제 이제 React native에서 Flutter로 갈아타기는 마무리하는게 좋겠어. 물론 React Query 같은 Data fetch는 매우 중요해서 비교를 하려고 했는데 비슷한게 Flutter에 있는지 아직 확인을 못했기도 하고 Stream builder같은 React에는 없는 것들도 막 튀어나오고 그래서 말야... 이제부터는 React native와 비교 없이 Flutter만 집중해서 하도록 할게.

이제 Firebase auth, firestore data fetch, firebase storage file upload 를 해볼건데 이거 끝나면 실제로 프로젝트 기획해서 upload까지 달려볼거야. 안녕!

반응형