Flutter で画面遷移の検知をするのには routeObserver.subscribe(...)
したり with RouteAware
する必要があって、結構めんどい。(参考: 【Flutter】RouteAwareで遷移を検知する方法 | 417.Run())
理想としては、 final appLifecycleState = useAppLifecycleState();
みたいに、 final routeAware = useRouteAwareEvent();
という形で custom hooks 形式で利用したい。
試行錯誤した結果、final routeAware = useRouteAwareEvent(ref.watch(routeObserverProvider));
で利用できるようになった。
useAppLifecycleState を参考に実装した。
実装
依存
以下に依存している。
- go_router
- go_router_builder
- flutter_riverpod
- riverpod_annotation
- flutter_hooks
- riverpod_generator
実装内容
まず RouteObserver をつくる。
@Riverpod(keepAlive: true) RouteObserver routeObserver(RouteObserverRef ref) => RouteObserver();
GoRouter でそれを利用する。
@Riverpod(keepAlive: true) GoRouter makeRouter(MakeRouterRef ref) => GoRouter( initialLocation: '/', routes: $appRoutes, observers: [ FirebaseAnalyticsObserver(analytics: FirebaseAnalytics.instance), ref.watch(routeObserverProvider), ], );
custom hooks を実装する。
// RouteAware は abstract class なのでコールバックするだけの実装をする class RouteAwareEvent extends RouteAware { VoidCallback? onDidPopNext; VoidCallback? onDidPop; VoidCallback? onDidPushNext; VoidCallback? onDidPush; @override void didPopNext() => onDidPopNext?.call(); @override void didPop() => onDidPop?.call(); @override void didPushNext() => onDidPushNext?.call(); @override void didPush() => onDidPush?.call(); } // hooks の返り値 enum RouteAwareType { didPush, didPop, didPopNext, didPushNext, } RouteAwareType? useRouteAwareEvent(RouteObserver routeObserver) { return use(_RouteAwareHook(routeObserver)); } class _RouteAwareHook extends Hook<RouteAwareType?> { const _RouteAwareHook(this.routeObserver) : super(); final RouteObserver routeObserver; @override __RouteAwareState createState() => __RouteAwareState(); } class __RouteAwareState extends HookState<RouteAwareType?, _RouteAwareHook> { RouteAwareType? _state; final aware = RouteAwareEvent(); @override void initHook() { super.initHook(); aware.onDidPop = () => _state = RouteAwareType.didPop; aware.onDidPopNext = () => _state = RouteAwareType.didPopNext; aware.onDidPush = () => _state = RouteAwareType.didPush; aware.onDidPushNext = () => _state = RouteAwareType.didPushNext; } var firstBuild = true; @override RouteAwareType? build(BuildContext context) { // initHook で context を触るとエラーになるので、ここで初期化する // また、ここでは useEffect が使えないので、firstBuild を利用し初回のみ subscribe する if (firstBuild) { firstBuild = false; hook.routeObserver.subscribe(aware, ModalRoute.of(context)!); } return _state; } @override void dispose() { super.dispose(); hook.routeObserver.unsubscribe(aware); } }
Widget ではこのように利用できる。
class HogePage extends HookConsumerWidget { HogePage({ Key? key, }) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { final routeAware = useRouteAwareEvent(ref.watch(routeObserverProvider)); useEffect(() { if (routeAware == RouteAwareType.didPop) { print('did pop'); } return null; }, [routeAware]); return Scaffold(); } }
おわり。