DAY 17 | Flutter 데모 앱 만들기: Route를 통한 페이지 연결

Pt J·2026년 8월 7일
post-thumbnail

DAY 17 | Flutter 데모 앱 만들기: Route를 통한 페이지 연결

Flutter에서 사용할 수 있는 Route

앱 화면을 관리할 때 Route를 사용한다. Route는 화면 정보와 화면들의 경로, 전환 정보를 담고 있다. Flutter 앱 개발에서 주로 쓰이는 Route package로는 go_routerauto_route가 있다. go_route는 Flutter SDK에 포함되어 있지는 않지만 Flutter 팀에서 관리하는 package라 압도적으로 많이 쓰인다.

예전에는 화면 정보를 담는 Route Stack에 후입선출의 형태로 페이지를 차곡차곡 쌓아 가장 위에 있는 것만 렌더링하는 것만 신경쓰면 되었다. 요즘은 모바일 시장이 성장하며 앱 복잡도가 높아졌기 때문에 데이터를 어떻게 주고받는지 등 추가적인 부분도 신경을 써 줘야 한다.

우리는 go_route를 사용할 것인데 이 녀석은 두 가지 방식의 Route를 제공한다. push 방식과 go 방식 중 go 방식이 이 package가 지향하는 방식이고, push 방식은 기존 개발자들을 위한 과도기적 인터페이스에 가깝다.

push 방식과 go 방식

pushgo
imperative (명령형)declarative (선언형)
모바일에선 좋지만 웹에는 부적절모바일과 웹 모두 적절
deep link 부적절deep link 잘 동작
context.push('/')context.go('/')

여기서 deep link는 앱의 특정 화면을 열 수 있게 해주는 URL이다. 부적절하다는 것은 불가능한 건 아닌데 구현이 필요 이상으로 복잡해지는 것을 말한다.

push 방식을 사용하면 사용자의 행동에 따라 Route Stack이 쌓인다. 이곳저곳 돌아다니다 보면 Route Stack이 메모리를 가득 채우기도 한다. 뒤로가기를 하면 그 동안 쌓여 있던 화면을 모두 거쳐 처음 위치로 돌아간다.

go 방식을 사용하면 사용자가 어떤 경로로 그곳에 도달했든 간에 사전에 선언되어 있는 구조로 Route Stack이 쌓인다. 따라서 개발자가 예측 가능한 범위에서 흘러간다.

go 방식도 Route Stack을 안 쓰는 건 아니다. 내부적으로는 Stack 구조를 가지는데 Stack 관리를 개발자가 하지 않고 package 내부에서 수행할 뿐이다.

Route를 통해 이동하고 데이터 전달하기

go_route를 통해 화면을 이동하고 데이터를 전달하는 방법을 알아보도록 하겠다.

먼저 pubspec.yaml 파일에 package를 추가한다.

pubspec.yaml

# 앞 부분 생략
dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.8
  audioplayers: ^6.8.1
  flex_color_scheme: ^8.4.0
  intl: ^0.20.3
  go_router: ^17.4.0
# 뒷 부분 생략

Route 파일을 생성하여 페이지 정보를 작성한다. go_route를 통해 페이지를 이동하려면 이렇게 별도로 페이지 정보를 담아놓는 GoRouter object가 필요하다. 이것을 MaterialApprouteConfig 로 전달해야 비로소 이동이 가능해진다. 선언되지 않은 곳으로는 이동할 수 없다.

workout_route.dart

import 'package:go_router/go_router.dart';
import 'landing_page.dart';
import 'workout_home_page.dart';
import 'workout_list_page.dart';
import 'workout_guide_page.dart';

final router = GoRouter(
  routes: [
    GoRoute(path: '/', builder: (context, state) => LandingPage()),
    GoRoute(
      path: '/workout_home',
      builder: (context, state) => WorkoutHomePage(),
      routes: [
        GoRoute(
          path: 'workout_list',
          builder: (context, state) => WorkoutListPage(),
          routes: [
            GoRoute(
              path: 'workout_guide',
              builder: (context, state) => WorkoutGuidePage(),
            ),
          ],
        ),
      ],
    ),
  ],
);

GoRoute 는 중첩될 수 있다. /route_name 은 최상위 Route이며, 뒤로가기를 하면 앱이 종료된다. 중첩되어 있는 하위 Route에서 뒤로가기를 누르면 사용자가 어떤 흐름으로 그 페이지에 도달하였든 중첩 상위 Route로 이동한다. '/workout_home' 하위에 있는'workout_list' 로 이동하고자 할 때는 '/workout_home/workout_list' 와 같이 적어주어야 한다.

lib/main.dart 에서 go_route를 사용하는 방식으로 코드를 수정하면 / 로 정의되어 있는 Route가 화면에 뜨고 사전에 선언한 Route를 따라 이동할 수 있게 된다.

lib/main.dart

import 'package:flutter/material.dart';
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'workout_router.dart';
// import 'landing_page.dart';
// import 'workout_list_page.dart';
// import 'workout_guide_page.dart';
// import 'workout_home_page.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      // home: const LandingPage(),
      // home: WorkoutListPage(),
      // home: WorkoutGuidePage(),
      // home: WorkoutHomePage(),
      routerConfig: router,
      theme: FlexThemeData.light(
        scheme: FlexScheme.blueWhale,
        fontFamily: 'Pretendard',
        subThemesData: const FlexSubThemesData(
          appBarBackgroundSchemeColor: SchemeColor.primary,
        )
      ),
    );
  }
}

페이지 이동하기

lib/landing_page.dart 하단의 [시작하기] 버튼을 누르면 WorkoutHomePage 로 이동하는 코드를 작성해 보자. 버튼을 클릭했을 때 context.go() 를 통해 '/workout_home' 으로 이동하게 하는 것이다.

lib/landing_page.dart

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

// 중략

SizedBox(
  width: 360,
  height: 60,
  child: ElevatedButton(
    onPressed: () {
      context.go('/workout_home'); // NEW!
    },
    style: ElevatedButton.styleFrom(
      backgroundColor: Colors.black87,
      shadowColor: Colors.white,
    ),
    child: Text('시작하기',
      style: TextStyle(fontSize: 24, color: Colors.white),
    ),
  ),
),

// 이하 생략

'/workout_home'LandingPage 에 해당하는 '/' 와 같은 level의 Route이기 때문에 Stack 위에 쌓이지 않고 대체된다. 따라서 뒤로가기를 누르면 LandingPage 로 돌아가지 않고 앱이 종료된다.

하위 페이지로 이동하기

WorkoutHomePageDashboardCard 를 클릭하면 WorkoutListPage 로 이동하고, WorkoutListPage 의 항목을 누르면 WorkoutGuidePage 로 이동하는 코드를 작성한다.

이에 앞서, DashboardCard 를 클릭 가능한 형태로 만들고 callback function을 통해 이동하고자 하는 Route를 받아야 한다.

lib/dashboard_card.dart

import 'package:flutter/material.dart';

class DashboardCard extends StatelessWidget {
  final Icon _labelIcon;
  final Text _labelText;
  final Widget _info;
  final Color _bgColor;
  final Function()? _routeOnTap;

  const DashboardCard({
    super.key,
    required this._labelIcon,
    required this._labelText,
    required this._info,
    this._bgColor = Colors.white,
    this._routeOnTap,
  });

  
  Widget build(BuildContext context) {
    ColorScheme colorScheme = Theme.of(context).colorScheme;

    return GestureDetector(
      onTap: _routeOnTap,
      child: Container(
        // 생략
      ),
    );
  }
}

callback function을 선택적으로 받아 onTap 에 전달한다.

WorkoutHomePage 의 [그룹 1], [그룹 2], [그룹 3]을 누르면 WorkoutListPage 로 연결되도록 작성한다. 그룹에 따라 적절한 운동 목록을 전달해야 하지만 우선은 페이지 연결만 하고 이후에 원하는 데이터를 전달하는 방식으로 수정하겠다.

lib/workout_home_page.dart

// 앞 부분 생략
AspectRatio(
  aspectRatio: 1 / 1,
  child: DashboardCard(
    routeOnTap: () {
       context.go('/workout_home/workout_list'); // NEW!
    },
    labelIcon: Icon(
      Icons.run_circle_outlined,
      size: textTheme.labelLarge?.fontSize,
      fontWeight: FontWeight.bold,
    ),
    labelText: Text(
      '그룹 1',
      style: textTheme.labelLarge?.copyWith(
        fontWeight: FontWeight.bold,
      ),
    ),
    info: Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        Flexible(
          flex: 4,
          child: Image.asset('assets/home/group1.png'),
        ),
        Flexible(
          flex: 6,
          child: Text(
            '아침을 여는\n5가지 운동',
            style: textTheme.bodyMedium,
          ),
        ),
      ],
    ),
    bgColor: Colors.yellow.shade50,
  ),
),
// 뒷 부분 생략

데이터 전달하기

마찬가지로 WorkoutListPage 에서도 항목을 클릭하여 WorkoutGuidePage 로 이동하도록 코드를 작성해야 하는데, 어떤 운동을 띄워줄 것인지 index를 전달해야 한다.

데이터 object를 직접 던져주는 전통적인 방식을 사용할 경우 deep link 사용 시 데이터 object를 받지 못해 화면을 제대로 띄워줄 수 없다. 따라서 Route의 URL에 데이터를 담아주어야 한다. Route에 데이터를 전달하는 방법은 크게 path parameter와 query parameter가 있다.

path parameter는 /workout_home/workout_list/worktout_guide/1 와 같은 형태로 작성하며 query parameter는 /workout_home/workout_list/workout_guide?id=1 와 같은 형태로 작성한다. path parameter를 사용하는 편이 더 안정적이고 체계적이다. 우리는 path parameter를 통해 데이터를 전달하도록 하겠다.

WorkoutGuidePage 를 열 때 어떤 운동을 나타낼 것인지 path parameter로 받겠다는 내용을 Route 정보 파일에 선언해 두어야 하므로 다음과 같이 내용을 수정한다.

lib/workout_router.dart

import 'package:go_router/go_router.dart';
import 'landing_page.dart';
import 'workout_home_page.dart';
import 'workout_list_page.dart';
import 'workout_guide_page.dart';

final router = GoRouter(
  routes: [
    GoRoute(path: '/', builder: (context, state) => LandingPage()),
    GoRoute(
      path: '/workout_home',
      builder: (context, state) => WorkoutHomePage(),
      routes: [
        GoRoute(
          path: 'workout_list',
          builder: (context, state) => WorkoutListPage(),
          routes: [
            GoRoute(
              // 여기서부터 수정됨.
              path: 'workout_guide/:workout_index',
              builder: (context, state) {
                String? workoutIndexStream =
                    state.pathParameters['workout_index'];
                final int workoutIndex = int.parse(workoutIndexStream!);
                return WorkoutGuidePage(workoutIndex: workoutIndex);
              },
            ),
          ],
        ),
      ],
    ),
  ],
);

그리고 이에 따라 WorkoutGuidePage 의 constructor도 workoutIndex 를 받는 방식으로 수정해 주어야 한다.

lib/workout_guide_page.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:audioplayers/audioplayers.dart';
import 'workout.dart';

class WorkoutGuidePage extends StatefulWidget {
  final int workoutIndex; // NEW!

  const WorkoutGuidePage({super.key, required this.workoutIndex}); // MODIFIED!

  
  State<WorkoutGuidePage> createState() => _WorkoutGuidePageState();
}

class _WorkoutGuidePageState extends State<WorkoutGuidePage> {
  final _player = AudioPlayer();
  Timer? _timer;
  bool _isPlaying = false;

  final List<Workout> workouts = [
    // 내용 생략
  ];

  late Workout _currentWorkout;
  late int _remainSeconds;
  int _workoutIndex = 0;

  
  void initState() {
    super.initState();
   _player.setReleaseMode(ReleaseMode.loop);
    _workoutIndex = widget.workoutIndex; // NEW!
    _currentWorkout = workouts[_workoutIndex];
    _remainSeconds = _currentWorkout.minutes * 60;
  }
  // 이하 생략
}

WorkoutListPage 의 각 항목을 클릭했을 때 그 항목에 해당하는 index가 담긴 Route로 이동하도록 코드를 작성한다.

lib/workout_list_page.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'workout.dart';

class WorkoutListPage extends StatelessWidget {
  WorkoutListPage({super.key});

  final List<Workout> workouts = [
    // 내용 생략
  ];

  List<ListTile> getWorkoutList(BuildContext context) {
    TextTheme textTheme = Theme.of(context).textTheme;
    ColorScheme colorScheme = Theme.of(context).colorScheme;

    List<ListTile> workoutTiles = [];

    for (var i = 0; i < workouts.length; i++) {
      String name = workouts[i].name;
      String image = workouts[i].imageName;
      int minutes = workouts[i].minutes;

      workoutTiles.add(
        ListTile(
          onTap: () {
            context.go('/workout_home/workout_list/workout_guide/$i'); // NEW!
          },
          // 생략
        ),
      };
    }
  }
  return workoutTiles;
}
// 이하 생략

데이터 따로 관리하기

지금은 WorkoutListPageWorkoutGuidePage 에 데이터가 중복 정의되어 있다. 앱 전체에서 접근할 수 있는 외부 팡리에 데이터를 담아놓고 가져다 쓰는 방식으로 수정하겠다.

lib/workout_manager.dart 파일을 작성하여 WorkoutManager class를 생성하고, instance 생성 없이 사용할 수 있는 static property로 데이터를 담아둘 것이다. static keyword가 붙으면 메모리에 상주하고 있어 이상적이지는 않지만 합리적인 이유로 실무에서는 많이 사용된다고 한다.

lib/workout_manager.dart

import 'workout.dart';

class WorkoutManager {
  static final List<Workout> workouts = [
    // 생략
  ];
}

이에 따라 WorkoutGuidePageWorkoutListPage 의 코드를 수정한다.

lib/workout_guide_page.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:audioplayers/audioplayers.dart';
import 'workout_manager.dart'; // NEW!
import 'workout.dart';

class WorkoutGuidePage extends StatefulWidget {
  final int workoutIndex;

  const WorkoutGuidePage({super.key, required this.workoutIndex});

  
  State<WorkoutGuidePage> createState() => _WorkoutGuidePageState();
}

class _WorkoutGuidePageState extends State<WorkoutGuidePage> {
  final _player = AudioPlayer();
  Timer? _timer;
  bool _isPlaying = false;
 
  List<Workout> workouts = WorkoutManager.workouts; // MODIFIED!

  // 생략
}

lib/workout_list_page.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'workout.dart';
import 'workout_manager.dart';

class WorkoutListPage extends StatelessWidget {
  WorkoutListPage({super.key});
 
  final List<Workout> workouts = WorkoutManager.workouts;

  // 생략
}

그룹 데이터 전달하기

그런데 지금은 어떤 그룹을 선택해도 같은 목록을 띄운다. 따라서 그룹을 관리하는 데이터를 생성하고 WorkoutHomePage 에서 어떤 DashboardCard 를 누르냐에 따라 다른 목록이 뜨도록 코드를 수정하겠다.

이를 위해 먼저 WorkoutGroup class 를 생성하고 WorkoutManager class에 그룹 목록을 생성한다.

lib/workout_group.dart

import 'workout.dart';

class WorkoutGroup {
  String groupDescription;
  List<Workout> workouts;

  WorkoutGroup({required this.groupDescription, required this.workouts});
}

lib/workout_mananger.dart

import 'workout.dart';
import 'workout_group.dart';

class WorkoutManager {
  static final List<Workout> workouts = [
    // 생략
  ];

  static List<WorkoutGroup> groups = [
    WorkoutGroup(
      groupDescription: '아침을 여는 5가지 운동',
      workouts: [
        workouts[0],
        workouts[2],
        workouts[5],
        workouts[3],
        workouts[7],
      ],
    ),
    WorkoutGroup(
      groupDescription: '근력을 키우는 7가지 운동',
      workouts: [
        workouts[1],
        workouts[2],
        workouts[3],
        workouts[6],
        workouts[7],
        workouts[8],
        workouts[0],
      ],
    ),
    WorkoutGroup(
      groupDescription: '하루를 마무리하는 4가지 운동',
      workouts: [workouts[3], workouts[0], workouts[2], workouts[6]],
    ),
  ];
}

주제랑 안 어울리는 운동이 들어가 있을 수 있지만 임의로 담아 보았다.

이와 같이 class를 생성하여 데이터를 관리하면 이해하기 쉽고 확장성도 좋다. 그룹 설명과 운동 목록뿐만 아니라 추가적인 데이터가 필요할 때 파편화된 데이터를 추가하는 게 아니라 class의 property를 추가하면 되니 말이다.

group_index 를 통해 원하는 그룹의 데이터를 화면에 띄워주도록 코드를 수정해 보자.

lib/workout_router.dart

import 'package:go_router/go_router.dart';
import 'landing_page.dart';
import 'workout_home_page.dart';
import 'workout_list_page.dart';
import 'workout_guide_page.dart';

final router = GoRouter(
  routes: [
    GoRoute(path: '/', builder: (context, state) => LandingPage()),
    GoRoute(
      path: '/workout_home',
      builder: (context, state) => WorkoutHomePage(),
      routes: [
        GoRoute(
          // 여기서부터 수정됨.
          path: 'workout_list/:group_index',
          builder: (context, state) {
            String? groupIndexStream = state.pathParameters['group_index'];
            final int groupIndex = int.parse(groupIndexStream!);
            return WorkoutListPage(groupIndex: groupIndex);
          },
          routes: [
            GoRoute(
              path: 'workout_guide/:workout_index',
              builder: (context, state) {
                String? workoutIndexStream =
                    state.pathParameters['workout_index'];
                final int workoutIndex = int.parse(workoutIndexStream!);
                return WorkoutGuidePage(workoutIndex: workoutIndex);
              },
            ),
          ],
        ),
      ],
    ),
  ],
);

Route가 변경됨에 따라 WorkoutListPage 의 constructor를 수정해야 하는데 여기서 중요한 건, class가 정의되는 시점에는 아직 데이터가 메모리에 올라가지 않았기 때문에 object를 참조할 수 없다는 것이다. 따라서 initializer list를 사용하여 object 생성 후에 초기화되도록 작성해야 한다.

lib/workout_list_page.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'workout.dart';
import 'workout_manager.dart';

class WorkoutListPage extends StatelessWidget {
  final int groupIndex;
  final List<Workout> workouts;
  WorkoutListPage({super.key, required this.groupIndex}):
    workouts = WorkoutManager.groups[groupIndex].workouts;

  // final List<Workout> workouts = WorkoutManager.workouts;
  // 생략
}

이제 WorkoutHomePage 의 적절한 DashboardCard 를 클릭했을 때 전체 목록이 아닌 원하는 그룹의 목록이 뜨도록 수정한다.

lib/workout_home_page.dart

// 앞 부분 생략
AspectRatio(
  aspectRatio: 1 / 1,
  child: DashboardCard(
    routeOnTap: () {
       context.go('/workout_home/workout_list/0'); // MODIFIED!
    },
    labelIcon: Icon(
      Icons.run_circle_outlined,
      size: textTheme.labelLarge?.fontSize,
      fontWeight: FontWeight.bold,
    ),
    labelText: Text(
      '그룹 1',
      style: textTheme.labelLarge?.copyWith(
        fontWeight: FontWeight.bold,
      ),
    ),
    info: Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        Flexible(
          flex: 4,
          child: Image.asset('assets/home/group1.png'),
        ),
        Flexible(
          flex: 6,
          child: Text(
            '아침을 여는\n5가지 운동',
            style: textTheme.bodyMedium,
          ),
        ),
      ],
    ),
    bgColor: Colors.yellow.shade50,
  ),
),
// 뒷 부분 생략

이제 잘 작동할 것 같지만 한 가지 문제가 있다!

WorkoutGuidePage 로 이동하는 코드를 다음과 같이 작성해 놓았는데,

context.go('/workout_home/workout_list/workout_guide/$i');

workout_listworkout_guide 사이에 group_index 가 끼어들면서 경로가 맞지 않게 되어 버렸다. 다음과 같이 경로를 수정해야 한다.

context.go('/workout_home/workout_list/$groupIndex/workout_guide/$i');

그런데 이렇게만 수정하면 WorkoutGuidePage 가 그룹 내 운동 목록이 아닌 전체 운동 목록을 기준으로 화면을 띄워주므로, WorkoutGuidePage 자체가 workoutManager.groups[groupIndex].workouts 를 기준으로 화면을 띄워주도록 코드를 수정해야 한다.

중첩되어 있는 Route는 상위 Route의 path parameter에 접근할 수 있으므로 이를 활용하여 WorkoutGuidePagegroupIndex 를 받게 수정한다.

lib/workout_router.dart

import 'package:go_router/go_router.dart';
import 'landing_page.dart';
import 'workout_home_page.dart';
import 'workout_list_page.dart';
import 'workout_guide_page.dart';

final router = GoRouter(
  routes: [
    GoRoute(path: '/', builder: (context, state) => LandingPage()),
    GoRoute(
      path: '/workout_home',
      builder: (context, state) => WorkoutHomePage(),
      routes: [
        GoRoute(
          path: 'workout_list/:group_index',
          builder: (context, state) {
            String? groupIndexStream = state.pathParameters['group_index'];
            final int groupIndex = int.parse(groupIndexStream!);
            return WorkoutListPage(groupIndex: groupIndex);
          },
          routes: [
            GoRoute(
              path: 'workout_guide/:workout_index',
              builder: (context, state) {
                String? workoutIndexStream =
                    state.pathParameters['workout_index'];
                final int workoutIndex = int.parse(workoutIndexStream!);
                // 여기서부터 수정됨.
                String? groupIndexStream = state.pathParameters['group_index'];
                final int groupIndex = int.parse(groupIndexStream!);
                return WorkoutGuidePage(
                  workoutIndex: workoutIndex,
                  groupIndex: groupIndex,
                );
              },
            ),
          ],
        ),
      ],
    ),
  ],
);

lib/workout_group_page.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:audioplayers/audioplayers.dart';
import 'workout_manager.dart';
import 'workout.dart';

class WorkoutGuidePage extends StatefulWidget {
  final int workoutIndex;
  final int groupIndex; // NEW!

  const WorkoutGuidePage({
    super.key,
    required this.workoutIndex,
    required this.groupIndex, // NEW!
  });

  
  State<WorkoutGuidePage> createState() => _WorkoutGuidePageState();
}

class _WorkoutGuidePageState extends State<WorkoutGuidePage> {
  final _player = AudioPlayer();
  Timer? _timer;
  bool _isPlaying = false;

  late List<Workout> workouts; // MODIFIED!

  late Workout _currentWorkout;
  late int _remainSeconds;
  int _workoutIndex = 0;

  
  void initState() {
    super.initState();
    _player.setReleaseMode(ReleaseMode.loop);
    workouts = WorkoutManager.groups[widget.groupIndex].workouts; // NEW!
    _workoutIndex = widget.workoutIndex;
    _currentWorkout = workouts[_workoutIndex];
    _remainSeconds = _currentWorkout.minutes * 60;
  }

  // 생략
}

이제 비로소 WorkoutHomePage 에서 원하는 그룹을 선택하면 해당 그룹의 운동 목록이 WorkoutListPage 로 뜨고, 거기서 또 원하는 운동을 선택하면 그 운동이 WorkoutGuidePage 로 뜨며, 좌우 화살표를 클릭하면 그 그룹의 운동 목록 내에서 순회하는 방식으로 정상적으로 작동한다.

좌측 iOS 시뮬레이터에는 [아침을 여는 5가지 운동]을, 우측 Android 애뮬레이터에는 [근력을 키우는 7가지 운동]을 띄워 보았다.

>>> GitHub Repository at this point (945dc03)

더 알아보기: Stateful Shell Route

bottom navigation bar라고 불리는, 하단 탭바를 구현할 때 유용한 기술이다. 하단 탭바를 구현하는 방식은 크게 세 가지가 있는데 특별한 일 없으면 Stateful Shell Route를 사용하는 게 좋다.

첫 번째 방식인 widget 방식은 모든 페이지에 sub widget을 넣고 어느 화면이 활성화되어 있는지 일일이 관리해야 하여 복잡하고 아무도 사용하지 않는 방식이다.

두 번째 방식인 Shell Route 방식은 Shell(껍데기)을 만들어 놓고 그 안에 화면 내용물을 넣어두는 방식이다. ShellRoute 에 하단 탭바와 Scaffold 를 넣어두고, route 될 때마다 Scaffold 를 바꿔 끼우는 거다. 어느 탭이 활성화되어 있는지 관리하기는 수월해졌지만 탭 이동 시 그 동안의 navigation stack history가 날아간다는 단점이 있다.

이를 보완한 게 세 번째 방식인 Stateful Shell Route 방식이다. 기본적으로 Shell Route 방식처럼 child 를 바꿔 끼우는 방식인데, 각 화면에서의 navigation stack을 State 데이터로 갖고 있는다. 이러한 방식으로 구현하면 홈 화면의 타임라인을 훑다가 탐색 탭으로 이동하여 이것저것 구경 후 다시 홈 화면의 타임라인으로 돌아왔을 때, 기존에 보고 있던 타임라인 화면을 이어서 볼 수 있다.

이에 대한 구체적인 구현 방식은 다음에 알아보기로 하고 오늘은 이론적인 부분만 살펴 보았다.

profile
Peter J Online Space - since July 2020 | 아무데서나 채용해줬으면 좋겠다 (지금은 학생 때 하던 거 아무거나 공부하고 있고요, 취업시켜 주시면 그 분야로 공부할게요)

0개의 댓글