docs

TOP(About this memo)) > 一覧(Flutter) > ルーティング

NavigatorとRouter

Navigator

Navigatorの不具合(24/10/11時点)

MaterialApp.routes(公式では推奨されていない)

// 公式サンプル
MaterialApp(
    routes: {
        '/': (context) => HomeScreen(),
        '/details': (context) => DetailScreen(),
    },
)
onPressed: () {
    Navigator.pushNamed(
        context,
        '/details',
        arguments: 3, // Object?型
    );
},

Router

Deep link

(参考)MaterialApp, CupertinoApp, WidgetsApp と Navigator

class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
    // ...
    bool get _usesRouterWithDelegates => widget.routerDelegate != null;
    bool get _usesRouterWithConfig => widget.routerConfig != null;
    bool get _usesNavigator => widget.home != null
        || (widget.routes?.isNotEmpty ?? false)
        || widget.onGenerateRoute != null
        || widget.onUnknownRoute != null;
    // ...
    Widget build(BuildContext context) {
        Widget? routing;
        if (_usesRouterWithDelegates) {
            routing = Router<Object>(/* ... */, routerDelegate: widget.routerDelegate!,);
        } else if (_usesNavigator) {
            routing = FocusScope(
                //...
                child: Navigator(/* .... */),
            );
        } else if (_usesRouterWithConfig) {
            routing = Router<Object>.withConfig(
                //...
                config: widget.routerConfig!,
            );
        }
    }
}

(参考)NavigatorStateの実装

class Navigator extends StatefulWidget {
    // ...
    final List<Page<dynamic>> pages;
    // ...
}
class NavigatorState extends State<Navigator> with TickerProviderStateMixin, RestorationMixin {
    //...
    _History _history = _History();
    //...
    bool get _usingPagesAPI => widget.pages != const <Page<dynamic>>[];// _usingPagesAPIはassertのみで利用。
    //...
    void initState() {
        //...
        _history.addListener(_handleHistoryChanged);
    }
    //...
    // restoreStateはState.initStateの直後に呼ばれる。
    @override
    void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
        //...
        for (final Page<dynamic> page in widget.pages) {
            final _RouteEntry entry = _RouteEntry(
                page.createRoute(context),
                pageBased: true,
                initialState: _RouteLifecycle.add,
            );
            //...
            _history.add(entry);
            _history.addAll(_serializableHistory.restoreEntriesForPage(entry, this));
        }
    }

    //...
    void _handleHistoryChanged() {
        //...
            notification.dispatch(context); // NavigationNotificationをdispatch
    }
    //...
    Future<T?> push<T extends Object?>(Route<T> route) {
        _pushEntry(_RouteEntry(route, pageBased: false, initialState: _RouteLifecycle.push));
        return route.popped;
    }
    //...
    void _pushEntry(_RouteEntry entry) {
        //...
        _history.add(entry);
        _flushHistoryUpdates();
        //...
    }
    void _flushHistoryUpdates({bool rearrangeOverlay = true}) {
        //...
        int index = _history.length - 1;
        _RouteEntry? next;
        _RouteEntry? entry = _history[index];
        _RouteEntry? previous = index > 0 ? _history[index - 1] : null;
        while (index >= 0) {
            switch (entry!.currentState) {
                //... 
            }
        }
        // ...
        if (rearrangeOverlay) {
            // overlay は _overlayKey.currentStateでOverlayStateを取得。(これはbuildで生成しているOverlayオブジェクトのState)
            // _allRouteOverlayEntriesでは、entry.route.overlayEntriesは_historyから取得した
            // Iterable<OverlayEntry>であり、これがOverlay._entriesへ追加・setStateされている。
            overlay?.rearrange(_allRouteOverlayEntries);
        }
        // ...
    }
    Widget build(BuildContext context) {
         NotificationListener<NavigationNotification>(
            //...
            Overlay(
                key: _overlayKey,
                initialEntries: overlay == null ?  _allRouteOverlayEntries.toList(growable: false) : const <OverlayEntry>[],
            )
    }
}
enum _RouteLifecycle {
  staging,
  add,
  adding,
  push,
  pushReplace,
  pushing,
  replace,
  idle,
  pop,
  complete,
  remove,
  popping,
  removing,
  dispose,
  disposing,
  disposed,
}
class _History extends Iterable<_RouteEntry> with ChangeNotifier {
    //...
}
class _RouteEntry extends RouteTransitionRecord {
    // ...
    final Route<dynamic> route;
    // ...
    _RouteLifecycle currentState;
}
abstract class Route<T> extends _RoutePlaceholder {
    //...
    NavigatorState? get navigator => _navigator;
    NavigatorState? _navigator;
    //...
    List<OverlayEntry> get overlayEntries => const <OverlayEntry>[];
}
class OverlayEntry implements Listenable {
    //...
    final WidgetBuilder builder;
    //...
}
class OverlayState extends State<Overlay> with TickerProviderStateMixin {
  final List<OverlayEntry> _entries = <OverlayEntry>[];

  @override
  void initState() {
    //...
    insertAll(widget.initialEntries);
  }
  //...
  void rearrange(Iterable<OverlayEntry> newEntries, { OverlayEntry? below, OverlayEntry? above }) {
    // ...
    setState(() {
        //...
        _entries.addAll(newEntriesList);
    });
  }

  Widget build(BuildContext context) {
    //...
    // 最終的に子孫の _OverlayEntryWidgetState.build()内で OverlayEntry.builder()が実行される。
  }
}

(参考)Routerについて

_RouterStateの実装

class _RouterState<T> extends State<Router<T>> with RestorationMixin {
    //...
    void initState() {
        super.initState();
        widget.routeInformationProvider?.addListener(_handleRouteInformationProviderNotification);
        widget.backButtonDispatcher?.addCallback(_handleBackButtonDispatcherNotification);
        widget.routerDelegate.addListener(_handleRouterDelegateNotification);
    }
    //...


    void _handleRouterDelegateNotification() {
        setState(() {/* routerDelegate wants to rebuild */});
        //...
    }

    //...
    void _handleRouteInformationProviderNotification() {
        // ...
        // 処理としてはwidget.routeInformationParser!.parseRouteInformationWithDependencies(widget.routeInformationProvider!.value, context) 
        //   ->  await widget.routerDelegate.setNewRoutePath(data); -> setStateされる。
        // dataはT型のルートのコンフィグ情報
    }
    //...

    Future<bool> _handleBackButtonDispatcherNotification() {
        //...
        return widget.routerDelegate
        .popRoute()
        .then<bool>(/* setStateしている */);
    }

    //...
    @override
    Widget build(BuildContext context) {
        return UnmanagedRestorationScope(
            bucket: bucket,
            child: _RouterScope(
                    //...
                    child: Builder(
                        builder: widget.routerDelegate.build,
                    ),
            ),
        );
    }
}