나는 이렇게 논다/Flutter 로 간단한 노트 앱을 만들어보자

Flutter로 간단한 노트 앱을 만들어보자 ③ - GoRouter로 네비게이션 바 리팩터링

daco2020 2024. 10. 31. 22:00
반응형

이번 글에서는 기존의 인덱스 방식의 화면 전환EnumGoRouter 를 사용하여 리팩터링 해보겠습니다.

 

 

GoRouter란?

GoRouter는 Flutter의 네비게이션을 간편하게 관리할 수 있도록 도와주는 패키지입니다. URL 기반의 라우팅을 지원하며 화면 전환과 관련된 복잡한 과정을 간단히 처리할 수 있습니다.

 

 

리팩터링 이유

기존의 탭 전환 방식에서는 네비게이션을 인덱스로 관리했는데, 이렇게 인덱스로 화면 전환을 관리하면 화면이 추가되거나 순서가 변경될 때 수정이 번거롭습니다. 배열 안에 정의된 화면의 순서를 모두 외우고 있어야 개발이 가능하죠.

 

그렇기 때문에 이번 글에서는 Enum 과 GoRouter 를 이용하여 각 화면을 명확히 구분하고, 경로 기반의 전환으로 더 명확하고 유지보수하기 쉬운 구조로 변경하겠습니다.

 

그럼 이제부터 본격적인 리팩터링을 시작해 보겠습니다.!

 

 

1. enum으로 탭 항목 정의

먼저 각 화면 탭을 enum으로 정의합니다. 

// lib/enums.dart

enum TabItem { noteDetail, noteList, settings }

 

이렇게 enum 으로 정의하면 인덱스 보다 직관적으로 페이지를 호출할 수 있습니다. 가독성뿐만 아니라 개발에서의 실수도 줄일 수 있죠.

 

 

2. GoRouter 설정 추가하기

GoRouter 를 사용하기 위해 먼저 패키지를 설치합니다.

flutter pub add go_router

 

설치가 끝났다면 main.dart 파일을 수정해 보겠습니다.

// lib/main.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'components/bottom_navbar.dart';
import 'views/note_detail_view.dart';
import 'views/note_list_view.dart';
import 'views/settings_view.dart';
import 'enums.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final GoRouter _router = GoRouter(  // 수정한 부분
    initialLocation: '/noteDetail',
    routes: [
      ShellRoute(
        builder: (context, state, child) {
          return HomeScreen(child: child);
        },
        routes: [
          GoRoute(
            path: '/noteDetail',
            name: TabItem.noteDetail.name,
            builder: (context, state) => NoteDetailView(),
          ),
          GoRoute(
            path: '/noteList',
            name: TabItem.noteList.name,
            builder: (context, state) => NoteListView(),
          ),
          GoRoute(
            path: '/settings',
            name: TabItem.settings.name,
            builder: (context, state) => SettingsView(),
          ),
        ],
      ),
    ],
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(  // 수정한 부분
      routerConfig: _router,
      theme: ThemeData.dark(),
    );
  }
}

 

각 화면을 URL 경로와 연결해 주면 화면 간 이동을 관리할 수 있는데요. ShellRoute를 사용해 HomeScreen의 네비게이션을 관리하도록 구성했습니다.

 

이렇게 만든 _router 를 기존 MaterialApp에 주입하기 위해 MaterialApp.router로 변경하여 넣습니다.

 

 

 

3. BottomNavBar 수정 및 탭 전환 로직 구현

기존 currentIndex 를 이번에 새로 정의한 TabItem 이넘으로 수정하도록 BottomNavBar 를 수정합니다. 주석으로 표시한 `수정한 부분`을 참고해 주세요.

// lib/components/bottom_navbar.dart
import 'package:flutter/material.dart';
import 'package:ttingnote/enums.dart';

class BottomNavBar extends StatelessWidget {
  final TabItem currentTab;
  final ValueChanged<TabItem> onTabSelected;

  const BottomNavBar({  // 수정한 부분
    Key? key,
    required this.currentTab,
    required this.onTabSelected,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      backgroundColor: Colors.black,
      selectedItemColor: Colors.white,
      unselectedItemColor: Colors.grey,
      currentIndex: TabItem.values.indexOf(currentTab),  // 수정한 부분
      onTap: (index) {  // 수정한 부분
        onTabSelected(TabItem.values[index]);
      },
      items: const [
        BottomNavigationBarItem(
          icon: Icon(Icons.create_outlined),
          label: '노트 쓰기',
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.list_alt_outlined),
          label: '노트 조회',
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.settings_outlined),
          label: '설정',
        ),
      ],
    );
  }
}

 

그리고 HomeScreen 에서는 탭이 선택될 때마다 GoRouter 를 통해 해당 경로로 이동하도록 설정하겠습니다.

// lib/main.dart - HomeScreen 클래스 수정
class HomeScreen extends StatefulWidget {
  final Widget child;

  const HomeScreen({Key? key, required this.child}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  TabItem _currentTab = TabItem.noteDetail;

  void _onTabSelected(TabItem tabItem) { // 수정한 부분
    setState(() {
      _currentTab = tabItem;

      switch (tabItem) {
        case TabItem.noteDetail:
          context.goNamed(TabItem.noteDetail.name);
          break;
        case TabItem.noteList:
          context.goNamed(TabItem.noteList.name);
          break;
        case TabItem.settings:
          context.goNamed(TabItem.settings.name);
          break;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(  // 수정한 부분
      body: widget.child,
      bottomNavigationBar: BottomNavBar(
        currentTab: _currentTab,
        onTabSelected: _onTabSelected,
      ),
    );
  }
}

 

이렇게 TabItem 이넘과 GoRouter 를 사용하여 코드를 리팩터링 해보았습니다.

 

결과 화면은?

 

 

 

이전과 다르지 않습니다. 이번 수정은 코드를 리팩터링만 한 것이기 때문에 기능적으로는 바뀐 게 없어요. 다만, 리팩터링 한 후에는 기존처럼 잘 동작하는지 확인이 꼭 필요합니다.

 

여기서 리팩터링을 마칠 수도 있지만,,, 개인적으로 화면 전환이 아쉽더군요. 오른쪽에서 왼쪽으로 전환되는 슬라이드 효과가 아닌 페이드인아웃 효과로 화면 전환을 바꿔보겠습니다.

 

 

4. 화면 전환을 페이드 효과로 변경

앞서 GoRouter 를 정의한 main.dart 파일로 다시 돌아갑니다.

 

우리는 화면 전환을 페이드 효과로 변경할 것이므로 FadeTransition을 활용하겠습니다. 먼저 FadeTransition 를 리턴하는 _fadeTransition 함수를 만들겠습니다.

// lib/main.dart

// 페이드 전환 애니메이션 설정
static Widget _fadeTransition(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child) {
  return FadeTransition(
    opacity: animation,
    child: child,
  );
}

 

그리고 상단에 정의된 GoRouterbuilder 파라미터를 pageBuilder 파라미터로 수정합니다. pageBuilder 를 사용하면 페이지 전환을 커스텀할 수 있습니다.

 

기존 코드:

           GoRoute(
             path: '/noteDetail',
             name: TabItem.noteDetail.name,
             builder: (context, state) => NoteDetailView())
           )

 

수정 코드:

           GoRoute(
             path: '/noteDetail',
             name: TabItem.noteDetail.name,
             pageBuilder: (context, state) => CustomTransitionPage(
               key: state.pageKey,
               child: NoteDetailView(),
               transitionDuration: const Duration(milliseconds: 200),
               transitionsBuilder: _fadeTransition,
             )
           )

 

나머지 /noteList/settings 부분도 동일하게 수정해 줍니다.

 

 

코드를 수정했다면 이제 화면 전환이 바뀌었는지 확인합시다.

 

 

 

우리가 의도한 대로 슬라이드에서 페이드로 화면 전환이 바뀐 것을 볼 수 있습니다.

 

 

마무리

이번 글에서는 기존의 인덱스 기반 화면 전환을 EnumGoRouter 를 사용해 리팩터링 하며 네비게이션 구조를 더 직관적이고 유지보수하기 쉽게 개선했습니다.

 

또한, 화면 전환 애니메이션을 슬라이드에서 페이드 효과로 변경해 보았습니다. 화면 전환 효과는 페이드 효과 외에도 다양하게 적용할 수 있으니 필요에 따라 커스텀하여 적용해 보시기 바랍니다.

 

 

[Flutter로 간단한 노트 앱을 만들어보자] 시리즈는 직접 독학으로 하나씩 만들어나가는 과정이므로 틀리거나 부족한 내용이 있을 수 있습니다. 조언과 피드백을 댓글로 남겨주시면 적극 반영하겠습니다. 감사합니다.
반응형