Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Master of Flutter lifecycle / Flutter Kaigi 2023

AAkira
November 10, 2023

Master of Flutter lifecycle / Flutter Kaigi 2023

This presentation is Master of Flutter lifecycle presented at Flutter Kaigi 2023.

* Flutter Kaigi 2023
https://flutterkaigi.jp/2023/

* My session
https://flutterkaigi.jp/2023/sessions/f76c37b8-172d-4072-ad4a-bd870bc15728

AAkira

November 10, 2023
Tweet

More Decks by AAkira

Other Decks in Technology

Transcript

  1. % About me 2 M3, Inc. Akira Aratani https://aakira.app https://aakira.studio

    🐕 ✈ 📷 🎾 🤸 🚁⊿ aakira.studio _a_akira AAkira chai_cavachon
  2. % M3, Inc. 4 https://www.m3tech.blog/ https://jobs.m3.com/engineer/ m3_engineering m3techchannel160 https://jobs.m3.com/product/ エンジニアリングの力で医療の世界を変えていく

    The Power of Medical Innovation Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  3. % • Flutter Lifecycle • Widget Lifecycle • App Lifecycle

    • Navigation & Routing • Handling Flutter lifecycle • Summary 5 Agenda
  4. % • Flutter Lifecycle • Widget Lifecycle • App Lifecycle

    • Navigation & Routing • Handling Flutter lifecycle • Summary 6 Agenda 目的 Flutterのライフサイクルの 仕組みを知って、 必要になった時に適切にイ ベントハンドリングできる ようになる。 OK CANCEL
  5. % • ライフサイクルは主に2つある • Widget自体のライフサイクル → 主にStateful Widget • アプリ全体のライフサイクル

    • OSに依るもの → WidgetBindingObserver • 画面遷移に依るもの → NavigatorObserver 11 Flutter lifecycle
  6. % • ライフサイクルは主に2つある • Widget自体のライフサイクル → 主にStateful Widget • アプリ全体のライフサイクル

    • OSに依るもの → WidgetBindingObserver • 画面遷移に依るもの → NavigatorObserver 12 Flutter lifecycle Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  7. % • ライフサイクルは主に2つある • Widget自体のライフサイクル → 主にStateful Widget • アプリ全体のライフサイクル

    • OSに依るもの → WidgetBindingObserver • 画面遷移に依るもの → NavigatorObserver 13 Flutter lifecycle Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  8. % • ライフサイクルは主に2つある • Widget自体のライフサイクル → 主にStateful Widget • アプリ全体のライフサイクル

    • OSに依るもの → WidgetBindingObserver • 画面遷移に依るもの → NavigatorObserver X Flutter lifecycle Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  9. % • createState • initState • didChangeDependencies • build •

    didUpdateWidget • setState • deactivate • dispose 20 Stateful Widgetで重要なライフサイクルのイベント
  10. % • createState • initState • didChangeDependencies • build •

    didUpdateWidget • setState • deactivate • dispose 21 Stateful Widgetで重要なライフサイクルのイベント createState initState didChangeDependencies deactivate dispose defunct ready created build didUpdateWidget setState initialized
  11. % createState initState didChangeDependencies deactivate dispose defunct ready created build

    didUpdateWidget setState initialized build 26 setState didUpdateWidget
  12. % @override Widget build(BuildContext context) { return Container( padding: const

    EdgeInsets.all(32), decoration: BoxDecoration( color: const Color(0xffd8e2ff), borderRadius: BorderRadius.circular(32), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Parent counter: $_counter'), const SizedBox(height: 8), OutlinedButton( onPressed: () => _incrementCounter(), child: const Text('Increment parent'), ), const SizedBox(height: 32), ChildWidget(), ], ), ); } 30 親Widet
  13. % @override Widget build(BuildContext context) { return Container( padding: const

    EdgeInsets.all(32), decoration: BoxDecoration( color: const Color(0xffd8e2ff), borderRadius: BorderRadius.circular(32), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Parent counter: $_counter'), const SizedBox(height: 8), OutlinedButton( onPressed: () => _incrementCounter(), child: const Text('Increment parent'), ), const SizedBox(height: 32), ChildWidget(), ], ), ); } 31 親Widet
  14. % @override Widget build(BuildContext context) { return Container( padding: const

    EdgeInsets.all(32), decoration: BoxDecoration( color: const Color(0xffd8e2ff), borderRadius: BorderRadius.circular(32), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Parent counter: $_counter'), const SizedBox(height: 8), OutlinedButton( onPressed: () => _incrementCounter(), child: const Text('Increment parent'), ), const SizedBox(height: 32), ChildWidget(), ], ), ); } 32 親Widet
  15. % @override Widget build(BuildContext context) { return Container( padding: const

    EdgeInsets.all(32), decoration: BoxDecoration( color: const Color(0xffd8e2ff), borderRadius: BorderRadius.circular(32), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Parent counter: $_counter'), const SizedBox(height: 8), OutlinedButton( onPressed: () => _incrementCounter(), child: const Text('Increment parent'), ), const SizedBox(height: 32), ChildWidget(), ], ), ); } 33 親Widet
  16. % @override Widget build(BuildContext context) { return Container( padding: const

    EdgeInsets.all(32), decoration: BoxDecoration( color: const Color(0xfffdd7fa), borderRadius: BorderRadius.circular(32), ), child: Column( children: [ Text('Child counter: $_counter'), const SizedBox(height: 8), OutlinedButton( onPressed: () => _incrementCounter(), child: const Text('Increment child'), ), ], ), ); } 34 子Widet
  17. % parent: initState parent: didChangeDependencies parent: build child: initState child:

    didChangeDependencies child: build 初回ビルド時 Child counter: 0 Parent counter: 0 Increment parent Increment child 36
  18. % Parentボタン押下時 Child counter: 5 Parent counter: 3 Increment parent

    Increment child parent: setState parent: build child: didUpdateWidget child: build 38
  19. % Parentボタン押下時 Child counter: 5 Parent counter: 3 Increment parent

    Increment child parent: setState parent: build child: didUpdateWidget child: build 親のWidgetが更新されたため呼ばれる 39
  20. % parent: deactivate child: deactivate child: dispose parent: dispose Widget破棄時

    Child counter: 5 Parent counter: 3 Increment parent Increment child 40
  21. % Parentボタン押下時 Child counter: 5 Parent counter: 3 Increment parent

    Increment child parent: setState parent: build child: didUpdateWidget child: build 42 子も更新されてしまう
  22. % @override Widget build(BuildContext context) { return Container( padding: const

    EdgeInsets.all(32), decoration: BoxDecoration( color: const Color(0xffd8e2ff), borderRadius: BorderRadius.circular(32), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Parent counter: $_counter'), const SizedBox(height: 8), OutlinedButton( onPressed: () => _incrementCounter(), child: const Text('Increment parent'), ), const SizedBox(height: 32), ChildWidget(), ], ), ); } 43 親Widet
  23. % ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Parent counter:

    $_count const SizedBox(height: 8), OutlinedButton( onPressed: () => _increment child: const Text('Incremen ), const SizedBox(height: 32), const ChildWidget(), ], ), ); } 44
  24. % Parentボタン押下時 Child counter: 5 Parent counter: 4 Increment parent

    Increment child parent: setState parent: build 親のみRebuildされる 45
  25. % Parentボタン押下時 Child counter: 5 Parent counter: 4 Increment parent

    Increment child parent: setState parent: build 親のみRebuildされる 46 Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  26. % Parentボタン押下時 Child counter: 5 Parent counter: 4 Increment parent

    Increment child parent: setState parent: build 親のみRebuildされる 47 Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  27. % Parentボタン押下時 Child counter: 5 Parent counter: 4 Increment parent

    Increment child parent: setState parent: build 親のみRebuildされる X Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  28. % • resumed • inactive • hidden → Flutter 3.13.0から追加

    • paused • detached 50 App lifecycle state https://docs. fl utter.dev/release/breaking-changes/add-applifecyclestate-hidden
  29. % 54 App Lifecycleの状態遷移図 resumed inactive hidden paused detached onInactive

    onHide onPause onDetach onResume onShow onRestart Foreground状態 Kill状態 Background状態 --------------------- ---------------------
  30. % • アプリからOSホームへの遷移 • OSホームからアプリへの遷移 • アプリからタスク一覧への遷移 • タスク一覧からアプリへの遷移 •

    画面OFF • 画面ON • アプリ終了 56 実際の挙動 • Noti fi cation, Control Centerを表示 • Noti fi cation, Control Centerを非表示 • 画面分割 • 画面分割時のフォーカス切り替え • 自分のアプリ側の画面分割を終了 • もう一方のアプリ側の画面分割を終了
  31. % ~3.13.0 • アプリ=>タスク一覧=>アプリ resumed • OSホーム=>タスク一覧=>アプリ inactive resumed タスク一覧からアプリへの遷移

    62 3.13.0~ • アプリ=>タスク一覧=>アプリ resumed • OSホーム=>タスク一覧=>アプリ hidden inactive resumed J04
  32. % ~3.13.0 • 開く inactive • 閉じる resumed 通知欄, 設定

    67 3.13.0~ • 開く inactive • 閉じる resumed J04
  33. % ~3.13.0 (inactive) (paused) resumed inactive resumed 画面分割 (Android) 69

    3.13.0~ (inactive) (hidden) (paused) hidden inactive resumed
  34. % 85 Android iOS(iPhone/iPad) アプリからOSホームへの遷移 inactive->hidden->paused inactive->hidden->paused OSホームからアプリへの遷移 hid->ina->res hid->ina->res

    アプリからタスク一覧への遷移 ina->hid->pau ina, (to home: hid->pau) タスク一覧からアプリへの遷移 hid->ina->res res, (from home: hid->ina->res) 画面OFF ina->hid->pau ina->hid->pau 画面ON hid->ina->res hid->ina->res Noti fi cation, Control Centerを表示 inactive inactive Noti fi cation, Control Centerを非表示 resumed resumed アプリ終了 ina->hid->pau->detached ina->hid->pau->detached 画面分割 hid->ina->res ina->res 画面分割時のフォーカス切り替え inactive/resumed - 自分のアプリ側の画面分割を終了 ina->hid->pau ina->hid->pau もう一方のアプリ側の画面分割を終了 focused: -, unfocused: resumed ina->res
  35. % 86 Android iOS(iPhone/iPad) アプリからOSホームへの遷移 inactive->hidden->paused inactive->hidden->paused OSホームからアプリへの遷移 hid->ina->res hid->ina->res

    アプリからタスク一覧への遷移 ina->hid->pau ina, (to home: hid->pau) タスク一覧からアプリへの遷移 hid->ina->res res, (from home: hid->ina->res) 画面OFF ina->hid->pau ina->hid->pau 画面ON hid->ina->res hid->ina->res Noti fi cation, Control Centerを表示 inactive inactive Noti fi cation, Control Centerを非表示 resumed resumed アプリ終了 ina->hid->pau->detached ina->hid->pau->detached 画面分割 hid->ina->res ina->res 画面分割時のフォーカス切り替え inactive/resumed - 自分のアプリ側の画面分割を終了 ina->hid->pau ina->hid->pau もう一方のアプリ側の画面分割を終了 focused: -, unfocused: resumed ina->res
  36. % 88 class ExamplePage extends StatefulWidget { const ExamplePage({super.key}); @override

    State<ExamplePage> createState() => _ExamplePageState(); } class _ExamplePageState extends State<ExamplePage> with WidgetsBindingObserver { @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { print(state); } @override Widget build(BuildContext context) { return Contaienr(); } } WidgetsBindingObserver
  37. % 89 const ExamplePage({super.key}); @override State<ExamplePage> createState() => _ExamplePageState(); }

    class _ExamplePageState extends State<ExamplePage> with WidgetsBindingObserver { @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { print(state); } @override Widget build(BuildContext context) { return Contaienr();
  38. % 90 const ExamplePage({super.key}); @override State<ExamplePage> createState() => _ExamplePageState(); }

    class _ExamplePageState extends State<ExamplePage> with WidgetsBindingObserver { @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { print(state); } @override Widget build(BuildContext context) { return Contaienr();
  39. % 91 const ExamplePage({super.key}); @override State<ExamplePage> createState() => _ExamplePageState(); }

    class _ExamplePageState extends State<ExamplePage> with WidgetsBindingObserver { @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { print(state); } @override Widget build(BuildContext context) { return Contaienr();
  40. % 92 const ExamplePage({super.key}); @override State<ExamplePage> createState() => _ExamplePageState(); }

    class _ExamplePageState extends State<ExamplePage> with WidgetsBindingObserver { @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { print(state); } @override Widget build(BuildContext context) { return Contaienr();
  41. % 93 class ExamplePage extends StatefulWidget { const ExamplePage({super.key}); @override

    State<ExamplePage> createState() => _ExamplePageState(); } class _ExamplePageState extends State<ExamplePage> { late final AppLifecycleListener appLifecycleListener; @override void initState() { super.initState(); appLifecycleListener = AppLifecycleListener( onShow: () => print('onShow'), onResume: () => print('onResume'), onHide: () => print('onHide'), onInactive: () => print('onInactive'), onPause: () => print('onPause'), onDetach: () => print('onDetach'), onRestart: () => print('onRestart'), onStateChange: (state) => print(state.toString()), ); } @override void dispose() { appLifecycleListener.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container(); } } AppLifecycleListener
  42. % 94 class ExamplePage extends StatefulWidget { const ExamplePage({super.key}); @override

    State<ExamplePage> createState() => _ExamplePageState(); } class _ExamplePageState extends State<ExamplePage> { late final AppLifecycleListener appLifecycleListener; @override void initState() { super.initState(); appLifecycleListener = AppLifecycleListener( onShow: () => print('onShow'), onResume: () => print('onResume'), onHide: () => print('onHide'), onInactive: () => print('onInactive'), onPause: () => print('onPause'), onDetach: () => print('onDetach'), onRestart: () => print('onRestart'), onStateChange: (state) => print(state.toString()), ); }
  43. % 95 class ExamplePage extends StatefulWidget { const ExamplePage({super.key}); @override

    State<ExamplePage> createState() => _ExamplePageState(); } class _ExamplePageState extends State<ExamplePage> { late final AppLifecycleListener appLifecycleListener; @override void initState() { super.initState(); appLifecycleListener = AppLifecycleListener( onShow: () => print('onShow'), onResume: () => print('onResume'), onHide: () => print('onHide'), onInactive: () => print('onInactive'), onPause: () => print('onPause'), onDetach: () => print('onDetach'), onRestart: () => print('onRestart'), onStateChange: (state) => print(state.toString()), ); }
  44. % 96 class ExamplePage extends StatefulWidget { const ExamplePage({super.key}); @override

    State<ExamplePage> createState() => _ExamplePageState(); } class _ExamplePageState extends State<ExamplePage> { late final AppLifecycleListener appLifecycleListener; @override void initState() { super.initState(); appLifecycleListener = AppLifecycleListener( onStateChange: (state) => print(state.toString()), ); } @override void dispose() { appLifecycleListener.dispose(); super.dispose(); } @override
  45. % 97 void initState() { super.initState(); appLifecycleListener = AppLifecycleListener( onShow:

    () => print('onShow'), onResume: () => print('onResume'), onHide: () => print('onHide'), onInactive: () => print('onInactive'), onPause: () => print('onPause'), onDetach: () => print('onDetach'), onRestart: () => print('onRestart'), onStateChange: (state) => print(state.toString()), ); } @override void dispose() { appLifecycleListener.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container(); } }
  46. % 98 @override State<ExamplePage> createState() => _ExamplePageState(); } class _ExamplePageState

    extends State<ExamplePage> { late final AppLifecycleListener appLifecycleListener; @override void initState() { super.initState(); appLifecycleListener = AppLifecycleListener( onShow: () => print('onShow'), onResume: () => print('onResume'), onHide: () => print('onHide'), onInactive: () => print('onInactive'), onPause: () => print('onPause'), onDetach: () => print('onDetach'), onRestart: () => print('onRestart'), onStateChange: (state) => print(state.toString()), ); } @override void dispose() { appLifecycleListener.dispose(); super.dispose(); } AppLifecycleStateにはない
  47. % AppLifecycleState inactive hidden paused アプリからOSホームへの遷移 99 AppLifecycleListener onInactive onHide

    onPause AppLifecycleState AppLifecycleListener onRestart onShow onResume hidden inactive resumed OSホームからアプリへの遷移
  48. % AppLifecycleState inactive hidden paused アプリからOSホームへの遷移 100 onInactive onHide onPause

    AppLifecycleState onRestart onShow onResume hidden inactive resumed OSホームからアプリへの遷移 AppLifecycleListener AppLifecycleListener
  49. % 101 inactive hidden paused アプリからOSホームへの遷移 AppLifecycleState onInactive onHide onPause

    AppLifecycleState onRestart onShow onResume AppLifecycleListener AppLifecycleListener hidden inactive resumed OSホームからアプリへの遷移 Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  50. % 102 inactive hidden paused アプリからOSホームへの遷移 AppLifecycleState AppLifecycleListener AppLifecycleListener onInactive

    onHide onPause AppLifecycleState onRestart onShow onResume hidden inactive resumed OSホームからアプリへの遷移 Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  51. % X inactive hidden paused アプリからOSホームへの遷移 AppLifecycleState AppLifecycleListener AppLifecycleListener onInactive

    onHide onPause AppLifecycleState onRestart onShow onResume hidden inactive resumed OSホームからアプリへの遷移 Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  52. % • 現在主流のものは主に3パターン • Navigator (通称Navigator1.0) • Router (通称Navigator2.0) •

    ライブラリーを使う(サードパーティ含む) • fl utter.dev/go_router • codeness.ly/auto_route • tom.gilder.dev/routemaster • etc... 104 Navigator
  53. % • 現在主流のものは主に3パターン • Navigator (通称Navigator1.0) • Router (通称Navigator2.0) •

    ライブラリーを使う(サードパーティ含む) • fl utter.dev/go_router • codeness.ly/auto_route • tom.gilder.dev/routemaster • etc... 105 Navigator https://docs. fl utter.dev/ui/navigation
  54. % 107 Page1 Page2 Page3 Page1 Page2 Page3 Page4 Page1

    Page2 Page3 Navigator Router (GoRouter etc...) Widget
  55. % • Navigator history stackの変化を監視 • didPush • didPop •

    didRemove • didReplace • (didStartUserGesture) • (didStopUserGesture) 108 NavigatorObserver
  56. % 109 class NavigatorObserver { NavigatorState? get navigator => _navigators[this];

    static final Expando<NavigatorState> _navigators = Expando<NavigatorState>(); void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { } void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { } void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { } void didReplace({ Route<dynamic>? newRoute, Route<dynamic>? oldRoute }) { } void didStartUserGesture(Route<dynamic> route, Route<dynamic>? previousRoute) { } void didStopUserGesture() { } }
  57. % 111 abstract mixin class RouteAware { void didPopNext() {

    } void didPush() { } void didPop() { } void didPushNext() { } }
  58. % 112 abstract mixin class RouteAware { void didPopNext() {

    } void didPush() { } void didPop() { } void didPushNext() { } } Page1 Page2 対象WidgetがPushされたタイミング
  59. % 113 abstract mixin class RouteAware { void didPopNext() {

    } void didPush() { } void didPop() { } void didPushNext() { } } 対象Widgetの上に別のWidgetが Pushされたタイミング Page1 Page2 Page3
  60. % 114 abstract mixin class RouteAware { void didPopNext() {

    } void didPush() { } void didPop() { } void didPushNext() { } } 対象Widgetの上にある別のWidgetが Popされたタイミング Page1 Page2 Page3
  61. % 115 abstract mixin class RouteAware { void didPopNext() {

    } void didPush() { } void didPop() { } void didPushNext() { } } 対象WidgetがPopされたタイミング Page1 Page2
  62. % 116 class RouteObserver<R extends Route<dynamic>> extends NavigatorObserver { @override

    void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { ... } @override void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { ... } ... }
  63. % 118 final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>(); Navigator( pages: [

    ... ], onPopPage: (route, result) { ... } ); MaterialApp( routes: <String, WidgetBuilder>{ ... }, ); GoRouter( initialLocation: '/', routes: [ ... ], ); Navigator1.0 Navigator2.0(Router) GoRouter
  64. % 119 final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>(); Navigator( observers: [

    routeObserver, ], pages: [ ... ], onPopPage: (route, result) { ... } ); MaterialApp( navigatorObservers: [ routeObserver, ], routes: <String, WidgetBuilder>{ ... }, ); GoRouter( observers: [ routeObserver, ], initialLocation: '/', routes: [ ... ], ); Navigator1.0 GoRouter Navigator2.0(Router)
  65. % 120 class ExamplePage extends StatefulWidget { const ExamplePage({ super.key

    }); @override State<StatefulWidget> createState() => _ExamplePageState(); } class _ExamplePageState extends State<ExamplePage> with RouteAware { @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe( this, ModalRoute.of(context)! as PageRoute<dynamic>, ); } @override void dispose() { routeObserver.unsubscribe(this); super.dispose(); } @override void didPush() { } @override void didPop() { } @override void didPushNext() { } @override void didPopNext() { } @override Widget build(BuildContext context) { return Container(); } }
  66. % class ExamplePage extends StatefulWidget { const ExamplePage({ super.key });

    @override State<StatefulWidget> createState() => _ExamplePageState(); } class _ExamplePageState extends State<ExamplePage> with RouteAware { @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe( this, ModalRoute.of(context)! as PageRoute<dynamic>, ); } @override 121 @override void didPush() } @override void didPop() } @override void didPushNe } @override void didPopNex } @override Widget build(B return Conta } }
  67. % 122 void didPush() } @override void didPop() } @override

    void didPushNe } @override void didPopNex } @override Widget build(B return Conta } } const ExamplePage({ super.key }); @override State<StatefulWidget> createState() => _ExamplePageState(); } class _ExamplePageState extends State<ExamplePage> with RouteAware { @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe( this, ModalRoute.of(context)! as PageRoute<dynamic>, ); } @override void dispose() { routeObserver.unsubscribe(this); super.dispose(); }
  68. % 123 @override void didPush() { } @override void didPop()

    { } @override void didPushNext() { } @override void didPopNext() { } @override Widget build(BuildContext context) { return Container(); } } extends StatefulWidget { e({ super.key }); dget> createState() => _ExamplePageState(); State extends State<ExamplePage> with RouteAware { pendencies() { eDependencies(); subscribe( f(context)! as PageRoute<dynamic>,
  69. % 124 void didPush() } @override void didPop() } @override

    void didPushNe } @override void didPopNex } @override Widget build(B return Conta } } const ExamplePage({ super.key }); @override State<StatefulWidget> createState() => _ExamplePageState(); } class _ExamplePageState extends State<ExamplePage> with RouteAware { @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe( this, ModalRoute.of(context)! as PageRoute<dynamic>, ); } @override void dispose() { routeObserver.unsubscribe(this); super.dispose(); }
  70. % void didPush() } @override void didPop() } @override void

    didPushNe } @override void didPopNex } @override Widget build(B return Conta } } const ExamplePage({ super.key }); @override State<StatefulWidget> createState() => _ExamplePageState(); } class _ExamplePageState extends State<ExamplePage> with RouteAware { @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe( this, ModalRoute.of(context)! as PageRoute<dynamic>, ); } @override void dispose() { routeObserver.unsubscribe(this); super.dispose(); } 125 Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  71. % void didPush() } @override void didPop() } @override void

    didPushNe } @override void didPopNex } @override Widget build(B return Conta } } const ExamplePage({ super.key }); @override State<StatefulWidget> createState() => _ExamplePageState(); } class _ExamplePageState extends State<ExamplePage> with RouteAware { @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe( this, ModalRoute.of(context)! as PageRoute<dynamic>, ); } @override void dispose() { routeObserver.unsubscribe(this); super.dispose(); } 126 Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  72. % void didPush() } @override void didPop() } @override void

    didPushNe } @override void didPopNex } @override Widget build(B return Conta } } const ExamplePage({ super.key }); @override State<StatefulWidget> createState() => _ExamplePageState(); } class _ExamplePageState extends State<ExamplePage> with RouteAware { @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe( this, ModalRoute.of(context)! as PageRoute<dynamic>, ); } @override void dispose() { routeObserver.unsubscribe(this); super.dispose(); } X Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  73. % 127 Handling Flutter lifecycle • 従来のAndroid, iOSのようなライフサイクルイベントは Flutterには用意されていない •

    Android: onResume, onPause, etc. iOS: viewDidAppear, viewDidDesappear, etc. • 画面遷移 • 端末の画面ON/OFF • アプリ自体のバックグラウンドへの移行イベント
  74. % 128 Handling Flutter lifecycle • 従来のAndroid, iOSのようなライフサイクルイベントは Flutterには用意されていない •

    Android: onResume, onPause, etc. iOS: viewDidAppear, viewDidDesappear, etc. • 画面遷移 • 端末の画面ON/OFF • アプリ自体のバックグラウンドへの移行イベント Handling lifecycle AppLifecycleListener (WidgetBindingObserver)と NavigationObserverを 組み合わせる必要がある OK CANCEL
  75. % 129 sealed class LifecycleEvent { const LifecycleEvent(this.isSystemEvent); final bool

    isSystemEvent; } class LifecycleEventResumed extends LifecycleEvent { const LifecycleEventResumed(super.isSystemEvent); @override String toString() => 'LifecycleEventResumed(isSystemEvent: $isSystemEvent)'; } class LifecycleEventPaused extends LifecycleEvent { const LifecycleEventPaused(super.isSystemEvent); @override String toString() => 'LifecycleEventPaused(isSystemEvent: $isSystemEvent)'; } typedef LifecycleObserverCallback = void Function(LifecycleEvent event);
  76. % 130 sealed class LifecycleEvent { const LifecycleEvent(this.isSystemEvent); final bool

    isSystemEvent; } class LifecycleEventResumed extends LifecycleEvent { const LifecycleEventResumed(super.isSystemEvent); @override String toString() => 'LifecycleEventResumed(isSystemEvent: $isSystemEvent)'; } class LifecycleEventPaused extends LifecycleEvent { const LifecycleEventPaused(super.isSystemEvent); @override String toString() => 'LifecycleEventPaused(isSystemEvent: $isSystemEvent)'; }
  77. % 131 sealed class LifecycleEvent { const LifecycleEvent(this.isSystemEvent); final bool

    isSystemEvent; } class LifecycleEventResumed extends LifecycleEvent { const LifecycleEventResumed(super.isSystemEvent); @override String toString() => 'LifecycleEventResumed(isSystemEvent: $isSystemEvent)'; } class LifecycleEventPaused extends LifecycleEvent { const LifecycleEventPaused(super.isSystemEvent); @override String toString() => 'LifecycleEventPaused(isSystemEvent: $isSystemEvent)'; } Navigation or 画面ON, OFFなどのイベントを 判別するため
  78. % 132 final bool isSystemEvent; } class LifecycleEventResumed extends LifecycleEvent

    { const LifecycleEventResumed(super.isSystemEvent); @override String toString() => 'LifecycleEventResumed(isSystemEvent: $isSystemEvent)'; } class LifecycleEventPaused extends LifecycleEvent { const LifecycleEventPaused(super.isSystemEvent); @override String toString() => 'LifecycleEventPaused(isSystemEvent: $isSystemEvent)'; } typedef LifecycleObserverCallback = void Function(LifecycleEvent event);
  79. % 133 class AppLifecycleObserver with RouteAware { AppLifecycleObserver({ required this.routeObserver,

    required ModalRoute<dynamic> route, required this.callback, }) { _appLifecycleListener = AppLifecycleListener( onResume: () { callback(const LifecycleEventResumed(true)); }, onPause: () { callback(const LifecycleEventPaused(true)); }, ); routeObserver.subscribe(this, route); } late final AppLifecycleListener _appLifecycleListener; final RouteObserver routeObserver; final LifecycleObserverCallback callback; @override void didPush() { callback(const LifecycleEventResumed(false)); } @override void didPop() { callback(const LifecycleEventPaused(false)); _appLifecycleListener.dispose(); } @override void didPushNext() { callback(const LifecycleEventPaused(false)); } @override void didPopNext() { callback(const LifecycleEventResumed(false)); } }
  80. % 134 class AppLifecycleObserver with RouteAware { AppLifecycleObserver({ required this.routeObserver,

    required ModalRoute<dynamic> route, required this.callback, }) { _appLifecycleListener = AppLifecycleListener( onResume: () { callback(const LifecycleEventResumed(true)); }, onPause: () { callback(const LifecycleEventPaused(true)); }, ); routeObserver.subscribe(this, route); } late final AppLifecycleListener _appLifecycleListener; final RouteObserver routeObserver; final LifecycleObserverCallback callback; @override void didPush() {
  81. % class AppLifecycleObserver with RouteAware { AppLifecycleObserver({ required this.routeObserver, required

    ModalRoute<dynamic> route, required this.callback, }) { _appLifecycleListener = AppLifecycleListener( onResume: () { callback(const LifecycleEventResumed(true)); }, onPause: () { callback(const LifecycleEventPaused(true)); }, ); routeObserver.subscribe(this, route); } late final AppLifecycleListener _appLifecycleListener; final RouteObserver routeObserver; final LifecycleObserverCallback callback; @override void didPush() { 135
  82. % class AppLifecycleObserver with RouteAware { AppLifecycleObserver({ required this.routeObserver, required

    ModalRoute<dynamic> route, required this.callback, }) { _appLifecycleListener = AppLifecycleListener( onResume: () { callback(const LifecycleEventResumed(true)); }, onPause: () { callback(const LifecycleEventPaused(true)); }, ); routeObserver.subscribe(this, route); } late final AppLifecycleListener _appLifecycleListener; final RouteObserver routeObserver; final LifecycleObserverCallback callback; @override void didPush() { 136
  83. % class AppLifecycleObserver with RouteAware { AppLifecycleObserver({ required this.routeObserver, required

    ModalRoute<dynamic> route, required this.callback, }) { _appLifecycleListener = AppLifecycleListener( onResume: () { callback(const LifecycleEventResumed(true)); }, onPause: () { callback(const LifecycleEventPaused(true)); }, ); routeObserver.subscribe(this, route); } late final AppLifecycleListener _appLifecycleListener; final RouteObserver routeObserver; final LifecycleObserverCallback callback; @override void didPush() { 137
  84. % class AppLifecycleObserver with RouteAware { AppLifecycleObserver({ required this.routeObserver, required

    ModalRoute<dynamic> route, required this.callback, }) { _appLifecycleListener = AppLifecycleListener( onResume: () { callback(const LifecycleEventResumed(true)); }, onPause: () { callback(const LifecycleEventPaused(true)); }, ); routeObserver.subscribe(this, route); } late final AppLifecycleListener _appLifecycleListener; final RouteObserver routeObserver; final LifecycleObserverCallback callback; @override void didPush() { 138
  85. % 139 late final AppLifecycleListener _appLifecycleListener; final RouteObserver routeObserver; final

    LifecycleObserverCallback callback; @override void didPush() { callback(const LifecycleEventResumed(false)); } @override void didPop() { callback(const LifecycleEventPaused(false)); _appLifecycleListener.dispose(); } @override void didPushNext() { callback(const LifecycleEventPaused(false)); } @override void didPopNext() { callback(const LifecycleEventResumed(false)); } }
  86. % late final AppLifecycleListener _appLifecycleListener; final RouteObserver routeObserver; final LifecycleObserverCallback

    callback; @override void didPush() { callback(const LifecycleEventResumed(false)); } @override void didPop() { callback(const LifecycleEventPaused(false)); _appLifecycleListener.dispose(); } @override void didPushNext() { callback(const LifecycleEventPaused(false)); } @override void didPopNext() { callback(const LifecycleEventResumed(false)); } } 140
  87. % 141 final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>(); Navigator( observers: [

    routeObserver, ], pages: [ ... ], onPopPage: (route, result) { ... } ); MaterialApp( navigatorObservers: [ routeObserver, ], routes: <String, WidgetBuilder>{ ... }, ); GoRouter( observers: [ routeObserver, ], initialLocation: '/', routes: [ ... ], ); Navigator1.0 GoRouter Navigator2.0(Router) 利用側
  88. % 142 利用側 class Page extends StatefulWidget { const Page({super.key});

    @override State<StatefulWidget> createState() => _PageState(); } class _PageState extends State<Page> { bool _initialized = false; @override void didChangeDependencies() { super.didChangeDependencies(); if(!_initialized) { AppLifecycleObserver( routeObserver: routeObserver, route: ModalRoute.of(context)!, callback: (event) { }, ); _initialized = true; } } @override Widget build(BuildContext context) { return Container(); } }
  89. % 143 t { ate() => eState(); class _PageState extends

    State<Page> { bool _initialized = false; @override void didChangeDependencies() { super.didChangeDependencies(); if(!_initialized) { AppLifecycleObserver( routeObserver: routeObserver, route: ModalRoute.of(context)!, callback: (event) { }, ); _initialized = true; } }
  90. % 144 t { ate() => eState(); class _PageState extends

    State<Page> { bool _initialized = false; @override void didChangeDependencies() { super.didChangeDependencies(); if(!_initialized) { AppLifecycleObserver( routeObserver: routeObserver, route: ModalRoute.of(context)!, callback: (event) { }, ); _initialized = true; } }
  91. % • 問題点 • 各Widgetでそれぞれ書くのは冗長 • Contextが必要 146 Handling Flutter

    lifecycle PageName(String)をKeyにしたLifecycleObserverを作ってみる
  92. % • 問題点 • 各Widgetでそれぞれ書くのは冗長 • Contextが必要 147 Handling Flutter

    lifecycle PageName(String)をKeyにしたLifecycleObserverを作ってみる 自作LifecycleObserverは参考程度に
  93. % 148 class AppLifecycleObserver extends NavigatorObserver { AppLifecycleObserver() { _appLifecycleListener

    = AppLifecycleListener( onResume: () { _emitLifecycleEvent(_routeStack.last, const LifecycleEventResumed(true)); }, onPause: () { _emitLifecycleEvent(_routeStack.last, const LifecycleEventPaused(true)); }, ); } late final AppLifecycleListener _appLifecycleListener; final Map<String, Set<LifecycleObserverCallback>> _subscribers = <String, Set<LifecycleObserverCallback>>{}; final List<Route<dynamic>> _routeStack = []; @override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { _routeStack.remove(route); _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } @override void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { _routeStack.add(route); _emitLifecycleEvent(previousRoute, const LifecycleEventPaused(false)); _emitLifecycleEvent(route, const LifecycleEventResumed(false)); } @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { if (_routeStack.last == route) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } _routeStack.remove(route); } @override void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) { final index = _routeStack.indexWhere((element) => element == oldRoute); if (_routeStack.last == oldRoute) { _emitLifecycleEvent(oldRoute, const LifecycleEventPaused(false)); } _routeStack.remove(oldRoute); if (index >= 0 && newRoute != null) { _routeStack.insert(index, newRoute); } if (_routeStack.last == newRoute) { _emitLifecycleEvent(newRoute, const LifecycleEventResumed(false)); } } void dispose() { _appLifecycleListener.dispose(); _subscribers.clear(); } void subscribe(String key, LifecycleObserverCallback callback) { final Set<LifecycleObserverCallback> subscribers = _subscribers.putIfAbsent(key, () => {}); subscribers.add(callback); if (_routeStack.isNotEmpty && _routeStack.last.settings.name == key) { _emitLifecycleEvent(_routeStack.last, const LifecycleEventResumed(false)); } } void unsubscribe(String key) { _subscribers.remove(key); } void _emitLifecycleEvent(Route<dynamic>? route, LifecycleEvent event) { final key = route?.settings.name; if (key == null) { return; } final subscribers = _subscribers[key]; if (subscribers == null) { return; } for (final subscriber in subscribers) { subscriber.call(event); } } }
  94. % 149 class AppLifecycleObserver extends NavigatorObserver { AppLifecycleObserver() { _appLifecycleListener

    = AppLifecycleListener( onResume: () { _emitLifecycleEvent(_routeStack.last, const LifecycleEventResumed(true)); }, onPause: () { _emitLifecycleEvent(_routeStack.last, const LifecycleEventPaused(true)); }, ); } late final AppLifecycleListener _appLifecycleListener; final Map<String, Set<LifecycleObserverCallback>> _subscribers = <String, Set<LifecycleObserverCallback>>{}; final List<Route<dynamic>> _routeStack = []; @override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { _routeStack.remove(route); @over void fin if _ } _ro if _ } if _ } } void _ap _su } void fin
  95. % 150 class AppLifecycleObserver extends NavigatorObserver { AppLifecycleObserver() { _appLifecycleListener

    = AppLifecycleListener( onResume: () { _emitLifecycleEvent(_routeStack.last, const LifecycleEventResumed(true)); }, onPause: () { _emitLifecycleEvent(_routeStack.last, const LifecycleEventPaused(true)); }, ); } late final AppLifecycleListener _appLifecycleListener; final Map<String, Set<LifecycleObserverCallback>> _subscribers = <String, Set<LifecycleObserverCallback>>{}; final List<Route<dynamic>> _routeStack = []; @override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { _routeStack.remove(route); @over void fin if _ } _ro if _ } if _ } } void _ap _su } void fin Navigation stack historyを管理
  96. % 151 class AppLifecycleObserver extends NavigatorObserver { AppLifecycleObserver() { _appLifecycleListener

    = AppLifecycleListener( onResume: () { _emitLifecycleEvent(_routeStack.last, const LifecycleEventResumed(true)); }, onPause: () { _emitLifecycleEvent(_routeStack.last, const LifecycleEventPaused(true)); }, ); } late final AppLifecycleListener _appLifecycleListener; final Map<String, Set<LifecycleObserverCallback>> _subscribers = <String, Set<LifecycleObserverCallback>>{}; final List<Route<dynamic>> _routeStack = []; @override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { _routeStack.remove(route); @over void fin if _ } _ro if _ } if _ } } void _ap _su } void fin StringのKeyでListenerの管理 Keyには route.nameを使う
  97. % 152 class AppLifecycleObserver extends NavigatorObserver { AppLifecycleObserver() { _appLifecycleListener

    = AppLifecycleListener( onResume: () { _emitLifecycleEvent(_routeStack.last, const LifecycleEventResumed(true)); }, onPause: () { _emitLifecycleEvent(_routeStack.last, const LifecycleEventPaused(true)); }, ); } late final AppLifecycleListener _appLifecycleListener; final Map<String, Set<LifecycleObserverCallback>> _subscribers = <String, Set<LifecycleObserverCallback>>{}; final List<Route<dynamic>> _routeStack = []; @override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { _routeStack.remove(route); @over void fin if _ } _ro if _ } if _ } } void _ap _su } void fin AppLifecycleListenerは同じ
  98. % 153 class AppLifecycleObserver extends NavigatorObserver { AppLifecycleObserver() { _appLifecycleListener

    = AppLifecycleListener( onResume: () { _emitLifecycleEvent(_routeStack.last, const LifecycleEventResumed(true)); }, onPause: () { _emitLifecycleEvent(_routeStack.last, const LifecycleEventPaused(true)); }, ); } late final AppLifecycleListener _appLifecycleListener; final Map<String, Set<LifecycleObserverCallback>> _subscribers = <String, Set<LifecycleObserverCallback>>{}; final List<Route<dynamic>> _routeStack = []; @override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { _routeStack.remove(route); @over void fin if _ } _ro if _ } if _ } } void _ap _su } void fin Navigation route の一番上にあるRouteに通知
  99. % 154 e)); )); lse)); _subscribers.putIfAbsent(key, () => {}); subscribers.add(callback);

    if (_routeStack.isNotEmpty && _routeStack.last.settings.name == key) { _emitLifecycleEvent(_routeStack.last, const LifecycleEventResumed(false)); } } void unsubscribe(String key) { _subscribers.remove(key); } void _emitLifecycleEvent(Route<dynamic>? route, LifecycleEvent event) { final key = route?.settings.name; if (key == null) { return; } final subscribers = _subscribers[key]; if (subscribers == null) { return; } for (final subscriber in subscribers) { subscriber.call(event); } } }
  100. % 155 @override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { _routeStack.remove(route);

    _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } @override void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { _routeStack.add(route); _emitLifecycleEvent(previousRoute, const LifecycleEventPaused(false)); _emitLifecycleEvent(route, const LifecycleEventResumed(false)); } @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { if (_routeStack.last == route) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } _routeStack.remove(route); } } void fin sub if _ } } void _su } void fin if r } fin if r } for
  101. % 156 @override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { _routeStack.remove(route);

    _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } @override void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { _routeStack.add(route); _emitLifecycleEvent(previousRoute, const LifecycleEventPaused(false)); _emitLifecycleEvent(route, const LifecycleEventResumed(false)); } @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { if (_routeStack.last == route) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } _routeStack.remove(route); } } void fin sub if _ } } void _su } void fin if r } fin if r } for
  102. % 157 @override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { _routeStack.remove(route);

    _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } @override void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { _routeStack.add(route); _emitLifecycleEvent(previousRoute, const LifecycleEventPaused(false)); _emitLifecycleEvent(route, const LifecycleEventResumed(false)); } @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { if (_routeStack.last == route) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } _routeStack.remove(route); } } void fin sub if _ } } void _su } void fin if r } fin if r } for
  103. % 158 true)); rue)); @override void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute})

    { final index = _routeStack.indexWhere((element) => element == oldRoute); if (_routeStack.last == oldRoute) { _emitLifecycleEvent(oldRoute, const LifecycleEventPaused(false)); } _routeStack.remove(oldRoute); if (index >= 0 && newRoute != null) { _routeStack.insert(index, newRoute); } if (_routeStack.last == newRoute) { _emitLifecycleEvent(newRoute, const LifecycleEventResumed(false)); } } void dispose() { _appLifecycleListener.dispose(); _subscribers.clear(); } void subscribe(String key, LifecycleObserverCallback callback) { final Set<LifecycleObserverCallback> subscribers = _subscribers.putIfAbsent(key, () => {}); subscribers.add(callback);
  104. % 159 true)); rue)); @override void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute})

    { final index = _routeStack.indexWhere((element) => element == oldRoute); if (_routeStack.last == oldRoute) { _emitLifecycleEvent(oldRoute, const LifecycleEventPaused(false)); } _routeStack.remove(oldRoute); if (index >= 0 && newRoute != null) { _routeStack.insert(index, newRoute); } if (_routeStack.last == newRoute) { _emitLifecycleEvent(newRoute, const LifecycleEventResumed(false)); } } void dispose() { _appLifecycleListener.dispose(); _subscribers.clear(); } void subscribe(String key, LifecycleObserverCallback callback) { final Set<LifecycleObserverCallback> subscribers = _subscribers.putIfAbsent(key, () => {}); subscribers.add(callback); Page1 Page2 Page3 Page1 Page4 Page3 Page1 Page2 Page3 Page1 Page2 Page4
  105. % 160 true)); rue)); @override void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute})

    { final index = _routeStack.indexWhere((element) => element == oldRoute); if (_routeStack.last == oldRoute) { _emitLifecycleEvent(oldRoute, const LifecycleEventPaused(false)); } _routeStack.remove(oldRoute); if (index >= 0 && newRoute != null) { _routeStack.insert(index, newRoute); } if (_routeStack.last == newRoute) { _emitLifecycleEvent(newRoute, const LifecycleEventResumed(false)); } } void dispose() { _appLifecycleListener.dispose(); _subscribers.clear(); } void subscribe(String key, LifecycleObserverCallback callback) { final Set<LifecycleObserverCallback> subscribers = _subscribers.putIfAbsent(key, () => {}); subscribers.add(callback);
  106. % 161 ; if (_routeStack.last == newRoute) { _emitLifecycleEvent(newRoute, const

    LifecycleEventResumed(false)); } } void dispose() { _appLifecycleListener.dispose(); _subscribers.clear(); } void subscribe(String key, LifecycleObserverCallback callback) { final Set<LifecycleObserverCallback> subscribers = _subscribers.putIfAbsent(key, () => {}); subscribers.add(callback); if (_routeStack.isNotEmpty && _routeStack.last.settings.name == key) { _emitLifecycleEvent(_routeStack.last, const LifecycleEventResumed(false)); } } void unsubscribe(String key) { _subscribers.remove(key); } void _emitLifecycleEvent(Route<dynamic>? route, LifecycleEvent event) { final key = route?.settings.name; if (key == null) {
  107. % 162 ; if (_routeStack.last == newRoute) { _emitLifecycleEvent(newRoute, const

    LifecycleEventResumed(false)); } } void dispose() { _appLifecycleListener.dispose(); _subscribers.clear(); } void subscribe(String key, LifecycleObserverCallback callback) { final Set<LifecycleObserverCallback> subscribers = _subscribers.putIfAbsent(key, () => {}); subscribers.add(callback); if (_routeStack.isNotEmpty && _routeStack.last.settings.name == key) { _emitLifecycleEvent(_routeStack.last, const LifecycleEventResumed(false)); } } void unsubscribe(String key) { _subscribers.remove(key); } void _emitLifecycleEvent(Route<dynamic>? route, LifecycleEvent event) { final key = route?.settings.name; if (key == null) {
  108. % 163 ; if (_routeStack.last == newRoute) { _emitLifecycleEvent(newRoute, const

    LifecycleEventResumed(false)); } } void dispose() { _appLifecycleListener.dispose(); _subscribers.clear(); } void subscribe( ɹ String key, LifecycleObserverCallback callback, { bool notifyImmediately = true, }) { final Set<LifecycleObserverCallback> subscribers = _subscribers.putIfAbsent(key, () => {}); subscribers.add(callback); if (notifyImmediately && _routeStack.isNotEmpty && _routeStack.last.settings.name == key) { _emitLifecycleEvent(_routeStack.last, const LifecycleEventResumed(false)); } } void unsubscribe(String key) { _subscribers.remove(key); すぐに通知されてしまうので、 適宜フラグで送らないようにするのも良い
  109. % 164 final goRouter = GoRouter( initialLocation: '/', observers: [

    appLifecycleObserver, ], routes: [ GoRoute( path: '/', pageBuilder: (context, state) { return MaterialPage( key: state.pageKey, name: '/', child: const MyHomePage(), ); }, ), GoRoute( path: '/page1', pageBuilder: (context, state) { return MaterialPage( key: state.pageKey, name: 'page1', child: const Page1(), ); }, ), GoRoute( path: '/page2', pageBuilder: (context, state) { return MaterialPage( key: state.pageKey, name: 'page2', child: const Page2(), ); }, ), ], ); 利用側
  110. % 165 final goRouter = GoRouter( initialLocation: '/', observers: [

    appLifecycleObserver, ], routes: [ GoRoute( path: '/', pageBuilder: (context, state) { return MaterialPage( key: state.pageKey, name: '/', child: const MyHomePage(), ); }, ), GoRoute( path: '/page1', pageBuilder: (context, state) { return MaterialPage( key: state.pageKey, name: 'page1', child: const Page1(), ); }, ), GoRoute( path: '/page2', pageBuilder: (context, state) { return MaterialPage( key: state.pageKey, name: 'page2', child: const Page2(), ); }, ), ], ); 利用側 MaterialPage.nameにnameをセット
  111. % 166 利用側 class _PageState extends State<Page> { bool _initialized

    = false; @override void didChangeDependencies() { super.didChangeDependencies(); if(!_initialized) { appLifecycleObserver.subscribe('pageName', (event) { switch (event) { case LifecycleEventResumed(): print('onResume: ${event.isSystemEvent}'); case LifecycleEventPaused(): print('onPause: ${event.isSystemEvent}'); } }); _initialized = true; } } @override Widget build(BuildContext context) { return Container(); } }
  112. % 167 利用側 class _PageState extends State<Page> { bool _initialized

    = false; @override void didChangeDependencies() { super.didChangeDependencies(); if(!_initialized) { appLifecycleObserver.subscribe('pageName', (event) { switch (event) { case LifecycleEventResumed(): print('onResume: ${event.isSystemEvent}'); case LifecycleEventPaused(): print('onPause: ${event.isSystemEvent}'); } }); _initialized = true; } } @override Widget build(BuildContext context) { return Container(); } }
  113. % 168 利用側 class _PageState extends State<Page> { bool _initialized

    = false; @override void initState() { super.initState(); if(!_initialized) { appLifecycleObserver.subscribe('pageName', (event) { switch (event) { case LifecycleEventResumed(): print('onResume: ${event.isSystemEvent}'); case LifecycleEventPaused(): print('onPause: ${event.isSystemEvent}'); } }); _initialized = true; } } @override Widget build(BuildContext context) { return Container(); } } Context不要なのでinitStateでも呼べる
  114. % 172 @riverpod Stream<LifecycleEvent> appLifecycleStream( AppLifecycleStreamRef ref, String key, )

    async* { StreamController<LifecycleEvent> eventController = StreamController(); final provider = ref.watch(appLifecycleObserverProvider); provider.subscribe(key, (LifecycleEvent event) { eventController.add(event); }); ref.onDispose(() { provider.unsubscribe(key); }); yield* eventController.stream; }
  115. % Stream<LifecycleEvent> appLifecycleStream( AppLifecycleStreamRef ref, String key, ) async* {

    StreamController<LifecycleEvent> eventController = StreamControll final provider = ref.watch(appLifecycleObserverProvider); provider.subscribe(key, (LifecycleEvent event) { eventController.add(event); }); ref.onDispose(() { provider.unsubscribe(key); }); yield* eventController.stream; } 173 appLifecycleObserverは内部で参照
  116. % 174 Stream<LifecycleEvent> appLifecycleStream( AppLifecycleStreamRef ref, String key, ) async*

    { StreamController<LifecycleEvent> eventController = StreamControll final provider = ref.watch(appLifecycleObserverProvider); provider.subscribe(key, (LifecycleEvent event) { eventController.add(event); }); ref.onDispose(() { provider.unsubscribe(key); }); yield* eventController.stream; } 渡されたKey(Route name)でsubscribe
  117. % 175 Stream<LifecycleEvent> appLifecycleStream( AppLifecycleStreamRef ref, String key, ) async*

    { StreamController<LifecycleEvent> eventController = StreamControll final provider = ref.watch(appLifecycleObserverProvider); provider.subscribe(key, (LifecycleEvent event) { eventController.add(event); }); ref.onDispose(() { provider.unsubscribe(key); }); yield* eventController.stream; } Disposeで自動unsubscribe
  118. % 178 @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { if

    (_routeStack.last == route) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } _routeStack.remove(route); } @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { final top = goRouter.routerDelegate.currentConfiguration.routes.lastOrNull; if (top != null && (top as GoRoute).name == route.settings.name) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } } 自分で管理 GoRouterを参照
  119. % 179 @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { if

    (_routeStack.last == route) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } _routeStack.remove(route); } @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { final top = goRouter.routerDelegate.currentConfiguration.routes.lastOrNull; if (top != null && (top as GoRoute).name == route.settings.name) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } } 自分で管理 GoRouterを参照
  120. % 180 @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { if

    (_routeStack.last == route) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } _routeStack.remove(route); } @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { final top = goRouter.routerDelegate.currentConfiguration.routes.lastOrNull; if (top != null && (top as GoRoute).name == route.settings.name) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } } 自分で管理 GoRouterを参照 Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  121. % 181 @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { if

    (_routeStack.last == route) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } _routeStack.remove(route); } @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { final top = goRouter.routerDelegate.currentConfiguration.routes.lastOrNull; if (top != null && (top as GoRoute).name == route.settings.name) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } } 自分で管理 GoRouterを参照 Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  122. % X @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { if

    (_routeStack.last == route) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } _routeStack.remove(route); } @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { final top = goRouter.routerDelegate.currentConfiguration.routes.lastOrNull; if (top != null && (top as GoRoute).name == route.settings.name) { _emitLifecycleEvent(route, const LifecycleEventPaused(false)); _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false)); } } 自分で管理 GoRouterを参照 Master of Flutter lifecycle Introduction Widget lifecycle App lifecycle Navigation & Routing Handling Flutter lifecycle Summary
  123. % • Widget, App, Navigationそれぞれのライフサイクルを学んだ • Flutter3.13.0からイベントの発火タイミングが変わったので注意が必要 • FlutterはEverything is

    a widgetになっているため、 画面遷移のライフサイクルイベントはNavigationを考慮する必要がある • それぞれを組み合わせて初めてFlutterのライフサイクルにあった処理ができる • 今日のサンプルコード https://github.com/AAkira/ fl utter_lifecycle_example 182 Summary