Don't think! Just do it!

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

Flutter/Flutter Study

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

방피터 2022. 10. 10. 17:52

React native에서는 react navigation를 사용했었는데.... react navigation 말고 다른 옵션이 있나?

음.. 암튼 React navigation에는 여러가지 navigator가 있는데 보통 Bottom tab navigator에 stack navigator를 합쳐서 사용해. Bottom tab으로 3-4개의 tab을 마련하고 그 안에 여러개의 screen이 stack navigator에 묶여 있는 거지.

보통 Bottom tab nav와 Stack nav를 함께 사용한다.

페이스북을 예로들면 6개의 tab이 있고 각 탭마다 여러개의 screen을 가지고 있는 stack navigator가 있어. 그래서 사용자가 댓글달기를 누르거나 사용자 사진을 누르면 tab은 가만히 있고 stack navigator 안의 스크린만 샥 이동하는 거지. 그래서 나같은 경우에는 navigators 폴더에 root navigator(bottom tab)와 각 tab의 숫자만큼 stack navigator 파일을 만들어서 screens 폴더에 있는 screen들을 불러와서 사용했어.(난 혼자해서 남들도 이렇게 하는지 잘 모름;;)

암튼 이런 구조는 사람들이 많이 익숙하기도 하고 개인적으로도 꽤 합리적이라고 생각하고 있어. 그래서 가능하다면 Flutter에서도 비슷한 구조를 사용해보려고 해! ㅎㅎㅎ 뭐 코드 형태가 조금(많이) 달라서 그렇지 ㅋㅋ 결국 비슷한 구조 아니겠어? ㅎㅎ 뭐 해보자구.

먼저 materialApp의 Bottom Navigation Bar에서 시작할거야  👇👇

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Navigation',
    home: MyApp(),
  ));
}

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

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

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Bottom Navigatiom Text")),
      body: Center(child: Text("Bottom Navigation Bar")),
      bottomNavigationBar: BottomNavigationBar(items: [
        BottomNavigationBarItem(icon: Icon(Icons.person), label: "Person"),
        BottomNavigationBarItem(icon: Icon(Icons.alarm), label: "alarm"),
        BottomNavigationBarItem(icon: Icon(Icons.umbrella), label: "umbrella")
      ]),
    );
  }
}

간단하게 BottomNavigationBar를 만들어 봤어. Icon을 아무거나 3개 넣어봤고. 여기까지는 뭐 일도 아니지. 그치? 그런데 이렇게하고 Bottom tab을 눌러보니까 아무 동작도 안해. 탭도 안움직이고.... React navigation은 탭은 그냥 됐던거 같은데.. 음 잘 생각안남.

간단하게 만들어지지만 아직 동작은 안한다

탭이 선택되도록 하려면 현재 탭을 기억하기 위한 state를 하나 만들어야 함. 그래서 statefulWidget으로 만든거임 ㅎㅎ 먼저 현재 tab을 기록할 수 있는 _currentTabIndex와 _tabSelect 함수를 만들어 bottomnavigationbar 위젯의 currentIndex와 onTap에 물려주면 끝.

class _MyAppState extends State<MyApp> {
  //여기서부터
  int _currentTabIndex = 0;
  void _tabSelect(int tabIndex) {
    setState(() {
      _currentTabIndex = tabIndex;
    });
  }
  //여기까지 추가
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Bottom Navigatiom Text")),
      body: Center(child: Text("Bottom Navigation Bar")),
      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),
        //여기까지 추가
      ),
    );
  }
}

그러면 아래와 같이 탭이 선택되는 걸 확인할 수 있어. 음 별거 한건 없지만 뿌듯하군! 

이제 탭이 선택된다.

그럼 bottom navigation tab은 됐고 body에 stack navigator를 구현해보자구. React navigator에서는 bottom tab에 screen 대신 stack navigator를 등록하는 방식이었어. 그런데 flutter에 stack navigator라는 게 있었나?... ㅎㅎㅎㅎㅎㅎ 몰라 찾아봤는데 없어! 걍 navigator를 사용해야 하나봐. 음 사용법은 크게 어렵지 않아. 👇👇 Navigator.push나 pop으로 이동하는 게 전부지 훗.

onPressed: () {
  Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => const ScreenB()),
  );
}

onPress: () {
  Navigator.pop(context);
}

그럼 쫄지 말고! 아놔 어떻게 하나?!! 고민하지 말고! 걍 닥치는대로 해보자구! 먼저 StatelessWidget ScreenA, B 두개를 만들거야. ScreenA.dart에서는 Screen B로 갈 수 있는 navigation 버튼을 하나 만들었고, ScreenB에서는 뒤로 가기 버튼을 하나 만들었어.

//ScreenA.dart

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

class ScreenA extends StatelessWidget {
  const ScreenA({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("ScreenA")),
        body: Center(
          child: Column(children: [
            Text("ScreenA"),
            TextButton(
              child: Text("Go to ScreenB"),
              onPressed: () {
                Navigator.push(context, MaterialPageRoute(
                  builder: (context) {
                    return const ScreenB();
                  },
                ));
              },
            )
          ]),
        ));
  }
}
//ScreenB.dart

import 'package:flutter/material.dart';

class ScreenB extends StatelessWidget {
  const ScreenB({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("ScreenB")),
        body: Center(
          child: Column(
            children: <Widget>[
              Text("ScreenB"),
              TextButton(
                onPressed: (() {
                  Navigator.pop(context);
                }),
                child: Text("Go back"),
              ),
            ],
          ),
        ));
  }
}

그리고 main scaffold의 body에서 아무렴 어때? 하고 호출해 보자구.

//main.dart
...
    return Scaffold(
      body: Center(child: ScreenA()),//ScreenA 위젯 불러옴!
      bottomNavigationBar: BottomNavigationBar(
        items: [
...

응 ScreenA가 나오긴 했는데 이러네 Bottom navigation이 사라지네 ㅎㅎㅎ 응 이렇게 하는 거 아니래~~ ㅎㅎ

응 bottom navigation tab사라져~ 이렇게 하는 거 아냐~

그러면 Bottom Navigation tab만 딱 놔두고 navigator가 동작되도록 해야지. 그렇게 하려면 그냥 screen 위젯을 리턴하면 안되고 Navigator를 리턴해야해. 우선 자꾸 파일이 많아지니까 폴더 정리부터 한번 하자구. 먼저 lib 폴더에 main.dart는 그냥 놔두고 screen이 많이 늘어날테니까 screens 폴더를 하나 만들고 그 다음에 각 tab 마다 navigator가 있을 수 있으니 navigators 폴더를 만들어봤어.

폴더 구조를 만들어가본다~

그 다음 ScreenA와 ScreenB.dart는 screens 폴더에 넣고 navigators 폴더에 PersonNavigator.dart라는 파일을 하나 만들었어. 파일 이름은 너무 신경쓰지 말자... 탭에 넣을 Icon이 마땅히 생각이 안나서 아무거나 넣은거야 ㅠㅠ

그리고 PersonNavigator 위젯을 작성해보면 아래와 같음!👇👇

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});

  Map<String, WidgetBuilder> _routeBuilder(BuildContext context) {
    return {
      "/": (context) => ScreenA(),		//"/ScreenA"로 시도해봤으나 안됌. "/" 필수
      "/ScreenB": (context) => ScreenB(),
    };
  }

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

설명할 코드도 없지만 그래도 잠깐 설명하자면 Screen이 아니라 Route 설정이 다 끝난 Navigator를 반환하는 위젯이야. intialRoute는 route의 첫 화면 그리고 onGenerateRoute에서는 setting을 통해 넘어오는 화면 이름("/", "/ScreenB")을 통해서 리턴되는 Screen이 달라지도록 설정하는 부분 정도만 눈여겨 보면 될 것 같음. 뒤에 "!" 는 JS랑 똑같아. 해당 변수가 절대 null이 아님! 이라는 소린데.. 많이 쓰면 혼나겠지?

이제 main.dart의 scaffold body에서 ScreenA() 대신 PersonNavigator()를 불러오자.👇👇

//main.dart
...
      //body: Center(child: ScreenA()),
      body: Center(child: PersonNavigator()),
...

그리고 ScreenA.dart Navigator push 하는 부분도 다이렉트로 push하는게 아니고 "/ScreenB"라는 이름으로 Push하게 변경하면 끝인 것 같아.

//ScreenA.dart
...
              onPressed: () {
                Navigator.pushNamed(context, '/ScreenB');
              },
...

이제 테스트를 해보면 Bottom이 안사라지고 잘 남아있는 걸 볼 수 있어. 하지만 각 탭마다 navigator가 별도로 설정된 게 아니어서 tab을 변경해도 body에 나오는 screen은 똑같지.👇👇👇

이제 Bottom tab도 잘 남아 있다.

다음 번에는 Tab마다 다른 네비게이터를 설정해볼거야. 각 스크린에 data도 넘겨주는 것도 해볼거구. 안녕!

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

 

반응형