Flutter에서 특정 라우트 페이지 제거하기

앱 개발 시, 페이지 전환 관리는 사용자 경험에 큰 영향을 미칩니다. 예를 들어, A 페이지에서 B 페이지로, 그리고 C 페이지로 순차적으로 이동한 후, 특정 조건에서 B 페이지를 제거하고 C 페이지에서 뒤로 가기 시 A 페이지로 돌아가도록 하는 요구사항이 발생할 수 있습니다. 본 글에서는 Flutter에서 이러한 페이지 내비게이션 패턴을 구현하는 방법을 알아봅니다.

Flutter에서는 일반적으로 라우트 설정을 통해 페이지를 관리합니다. 라우트 이름과 해당 페이지를 매핑하여 정의합니다.

MaterialApp(
  title: 'Flutter Demo',
  initialRoute: "/", // "/" 경로를 앱의 홈으로 설정
  routes: {
    "/": (context) => MyHomePage(title: 'Flutter Demo Home Page'), // 홈 페이지 라우트 등록
    "new_page": (context) => NewRoute(),
  },
);

기존 요구사항을 충족하려면, 페이지의 라우트 이름을 통해 특정 페이지를 제거하는 메서드가 필요합니다. 예를 들어 void removeName(String name)과 같은 형태입니다. 하지만 Flutter에는 직접적으로 이러한 기능을 제공하는 메서드가 없습니다. 대신 Navigator.of(context).removeRoute(route) 메서드를 사용하여 페이지를 제거할 수 있습니다.

Route 클래스를 살펴보면, RouteSettings 속성을 가지고 있으며, 이 안에는 우리가 정의한 라우트 이름이 포함된 name 속성이 있습니다.

abstract class Route<T> {
  /// Initialize the [Route].
  ///
  /// If the [settings] are not provided, an empty [RouteSettings] object is
  /// used instead.
  Route({ RouteSettings? settings }) : _settings = settings ?? const RouteSettings();
}
class RouteSettings {
  /// Creates data used to construct routes.
  const RouteSettings({
    this.name,
    this.arguments,
  });
}

문제는 Route 객체를 직접적으로 얻기 어렵다는 점입니다. 따라서 라우트 스택(route stack)을 별도로 기록하고, 라우트 이름을 통해 해당 route를 찾은 뒤 Navigator.of(context).removeRoute(route)를 호출하여 페이지를 제거해야 합니다.

구현 방법

라우트 설정

여기서는 GetX를 사용하여 라우트 관리를 하고, navigatorObservers를 통해 라우트 스택을 기록합니다. GetX를 선호하지 않는다면 Flutter 기본 방식으로도 동일하게 구현할 수 있습니다.

class MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Flutter Demo',
      initialRoute: RouteConfig.home,
      getPages: RouteConfig.getPages,
      navigatorObservers: [routeHistoryObserver], // 라우트 스택 기록
    );
  }
}

라우트 테이블 설정:

/// 라우트 설정 클래스
class RouteConfig {
  static const String home = "/home";
  static const String a = "/a";
  static const String b = "/b";
  static const String c = "/c";

  static final List<GetPage> getPages = [
    GetPage(name: home, page: () => const MyHomePage()),
    GetPage(name: a, page: () => const APage()),
    GetPage(name: b, page: () => const BPage()),
    GetPage(name: c, page: () => const CPage()),
  ];
}

라우트 스택 기록

RouteObserver를 사용하여 라우트 스택을 기록합니다. history 리스트를 통해 페이지의 푸시 및 팝 이벤트를 추적합니다.

HistoryRouteObserver routeHistoryObserver = HistoryRouteObserver();

/// 라우트 기록 관리 클래스
class HistoryRouteObserver extends RouteObserver<PageRoute> {
  List history = [];

  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    super.didPop(route, previousRoute);
    history.remove(route);
    // Navigator.of(context).pop() 호출 시 콜백
  }

  @override
  void didPush(Route route, Route? previousRoute) {
    super.didPush(route, previousRoute);
    history.add(route);
    // Navigator.of(context).push(Route()) 호출 시 콜백
  }

  @override
  void didRemove(Route route, Route? previousRoute) {
    super.didRemove(route, previousRoute);
    history.remove(route);
    // Navigator.of(context).removeRoute(Route()) 호출 시 콜백
  }

  @override
  void didReplace({Route? newRoute, Route? oldRoute}) {
    super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
    if (oldRoute != null) {
      history.remove(oldRoute);
    }
    if (newRoute != null) {
      history.add(newRoute);
    }
    // Navigator.of(context).replace(oldRoute: Route("old"), newRoute: Route("new")) 호출 시 콜백
  }
}

지정 페이지 삭제

GetInterface에 확장 메서드를 추가하여, name을 통해 특정 Route를 찾아 삭제합니다.

extension GetExtension on GetInterface {
  /// 라우트 기록
  List get history => routeHistoryObserver.history;

  /// 해당 페이지가 열려 있는지 확인
  bool containName(String name) {
    return getRouteByName(name) != null;
  }

  /// name으로 route 가져오기 (스택 상단부터 검색)
  Route? getRouteByName(String name) {
    var index = history.lastIndexWhere((element) => element.settings.name == name);
    if (index != -1) {
      return history[index];
    }
    return null;
  }

  /// name으로 route 가져오기
  List<Route> getRoutesByName(String name) {
    return history.where((element) => element.settings.name == name).toList();
  }

  /// 지정된 페이지 하나 삭제
  void removeName(String name) {
    var route = getRouteByName(name);
    if (route != null) {
      Get.removeRoute(route);
    }
  }

  /// 지정된 모든 페이지 삭제
  void removeAllName(String name) {
    var routes = getRoutesByName(name);
    for (var route in routes) {
      Get.removeRoute(route);
    }
  }
}

사용 예시

확장 메서드 Get.removeName(RouteConfig.b);를 사용하여 지정된 페이지를 제거할 수 있습니다.

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('CPage'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ElevatedButton(
              onPressed: () {
                Get.removeName(RouteConfig.a); // A 페이지 제거
              },
              child: const Text("Remove A"),
            ),
            ElevatedButton(
              onPressed: () {
                Get.removeName(RouteConfig.b); // B 페이지 제거
              },
              child: const Text("Remove B"),
            ),
          ],
        ),
      ),
    );
  }
}

태그: Flutter Dart Navigation routing getx

7월 12일 20:01에 게시됨