$30 off During Our Annual Pro Sale. View Details »

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. ""LJSB
    Master of Flutter lifecycle
    @_a_akira
    AAkira
    Flutter Kaigi 2023

    View Slide

  2. %
    About me
    2
    M3, Inc.
    Akira Aratani
    https://aakira.app
    https://aakira.studio
    🐕 ✈ 📷 🎾 🤸 🚁⊿
    aakira.studio
    _a_akira AAkira
    chai_cavachon

    View Slide

  3. %
    M3, Inc.
    3
    https://www.m3tech.blog/
    https://jobs.m3.com/engineer/
    m3_engineering m3techchannel160
    https://jobs.m3.com/product/
    エンジニアリングの力で医療の世界を変えていく
    The Power of Medical Innovation

    View Slide

  4. %
    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

    View Slide

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

    View Slide

  6. %
    • Flutter Lifecycle
    • Widget Lifecycle
    • App Lifecycle
    • Navigation & Routing
    • Handling Flutter lifecycle
    • Summary
    6
    Agenda
    目的
    Flutterのライフサイクルの
    仕組みを知って、
    必要になった時に適切にイ
    ベントハンドリングできる
    ようになる。
    OK
    CANCEL

    View Slide

  7. %
    Lifecycleとは?
    7

    View Slide

  8. %
    8
    https://developer.android.com/guide/components/activities/activity-lifecycle https://developer.apple.com/documentation/uikit/uiviewcontroller

    View Slide

  9. %
    • ライフサイクルは主に2つある
    • Widget自体のライフサイクル
    • アプリ全体のライフサイクル
    9
    Flutter lifecycle

    View Slide

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

    View Slide

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

    View Slide

  12. %
    • ライフサイクルは主に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

    View Slide

  13. %
    • ライフサイクルは主に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

    View Slide

  14. %
    • ライフサイクルは主に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

    View Slide

  15. %
    • StatelessWidget
    • StatefulWidget
    • ProxyWidget(InheritedWidget, ParentDataWidget)
    • RenderObjectWidget
    14
    Widget

    View Slide

  16. %
    • StatelessWidget
    • StatefulWidget
    • ProxyWidget(InheritedWidget, ParentDataWidget)
    • RenderObjectWidget
    15
    Widget

    View Slide

  17. %
    • StatelessWidget
    • StatefulWidget
    • ProxyWidget(InheritedWidget, ParentDataWidget)
    • RenderObjectWidget
    16
    Widget

    View Slide

  18. %
    • 状態を持つことを目的としたWidget
    • 状態があるView componentが多く利用している
    • Image, Button, Radio, DropDownMenu, Slider, Chip...
    17
    Stateful Widget

    View Slide

  19. %
    • _StateLifecycle
    • created
    • initialized
    • ready
    • defunct
    18
    State lifecycle

    View Slide

  20. %
    19
    State lifecycle
    初期値、Stateオブジェクトが生成された後
    Stateオブジェクトはまだない
    Stateオブジェクトが利用可能
    Stateオブジェクトは利用不可
    • _StateLifecycle
    • created
    • initialized
    • ready
    • defunct

    View Slide

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

    View Slide

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

    View Slide

  23. %
    createState
    initState
    didChangeDependencies
    deactivate
    dispose
    defunct
    ready
    created
    build
    didUpdateWidget setState
    initialized
    22

    View Slide

  24. %
    createState
    initState
    didChangeDependencies
    deactivate
    dispose
    defunct
    ready
    created
    build
    didUpdateWidget setState
    initialized
    23

    View Slide

  25. %
    createState
    initState
    didChangeDependencies
    deactivate
    dispose
    defunct
    ready
    created
    build
    didUpdateWidget setState
    initialized
    24

    View Slide

  26. %
    createState
    initState
    didChangeDependencies
    deactivate
    dispose
    defunct
    ready
    created
    build
    didUpdateWidget setState
    initialized
    25

    View Slide

  27. %
    createState
    initState
    didChangeDependencies
    deactivate
    dispose
    defunct
    ready
    created
    build
    didUpdateWidget setState
    initialized
    build
    26
    setState
    didUpdateWidget

    View Slide

  28. %
    createState
    initState
    didChangeDependencies
    deactivate
    dispose
    defunct
    ready
    created
    build
    didUpdateWidget setState
    initialized
    deactivate
    27
    dispose

    View Slide

  29. %
    • Flutterの描画にはWidget treeとElement tree, RenderObject treeがある
    • Elementにもライフサイクルが存在する(_ElementLifecycle)
    • initial
    • active
    • inactive
    • defunct
    28
    Element lifecycle

    View Slide

  30. %
    実際の挙動
    29

    View Slide

  31. %
    @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

    View Slide

  32. %
    @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

    View Slide

  33. %
    @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

    View Slide

  34. %
    @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

    View Slide

  35. %
    @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

    View Slide

  36. %
    35
    Child counter: 0
    Parent counter: 0
    Increment parent
    Increment child

    View Slide

  37. %
    parent: initState
    parent: didChangeDependencies
    parent: build
    child: initState
    child: didChangeDependencies
    child: build
    初回ビルド時
    Child counter: 0
    Parent counter: 0
    Increment parent
    Increment child
    36

    View Slide

  38. %
    child: setState
    child: build
    Childボタン押下時
    Child counter: 5
    Parent counter: 0
    Increment parent
    Increment child
    37

    View Slide

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

    View Slide

  40. %
    Parentボタン押下時
    Child counter: 5
    Parent counter: 3
    Increment parent
    Increment child
    parent: setState
    parent: build
    child: didUpdateWidget
    child: build
    親のWidgetが更新されたため呼ばれる
    39

    View Slide

  41. %
    parent: deactivate
    child: deactivate
    child: dispose
    parent: dispose
    Widget破棄時
    Child counter: 5
    Parent counter: 3
    Increment parent
    Increment child
    40

    View Slide

  42. %
    41
    didUpdateWidget呼ばれないようにする

    View Slide

  43. %
    Parentボタン押下時
    Child counter: 5
    Parent counter: 3
    Increment parent
    Increment child
    parent: setState
    parent: build
    child: didUpdateWidget
    child: build
    42
    子も更新されてしまう

    View Slide

  44. %
    @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

    View Slide

  45. %
    ),
    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

    View Slide

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

    View Slide

  47. %
    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

    View Slide

  48. %
    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

    View Slide

  49. %
    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

    View Slide

  50. %
    • OSによる操作をFlutterのフレームワークが吸収したもの
    • Flutter 3.13.0で大きな変更があった
    • 状態の追加(hidden)
    • イベントの発火パターンが増えた
    • 状態遷移の流れが変わった
    • AppLifecycleListener(WidgetBindingObserver)でハンドリング
    48
    App lifecycle

    View Slide

  51. %
    • resumed
    • inactive
    • hidden
    • paused
    • detached
    49
    App lifecycle state

    View Slide

  52. %
    • resumed
    • inactive
    • hidden → Flutter 3.13.0から追加
    • paused
    • detached
    50
    App lifecycle state
    https://docs.
    fl
    utter.dev/release/breaking-changes/add-applifecyclestate-hidden

    View Slide

  53. %
    51
    https://github.com/
    fl
    utter/engine/pull/40542

    View Slide

  54. %
    52
    https://github.com/
    fl
    utter/engine/pull/40542
    hidden
    3.13.0以前は非対応だった
    macOSとLinuxのために追加
    された
    OK
    CANCEL

    View Slide

  55. %
    53
    App Lifecycleの状態遷移図
    resumed
    inactive
    hidden
    paused
    detached
    onInactive
    onHide
    onPause
    onDetach
    onResume
    onShow
    onRestart

    View Slide

  56. %
    54
    App Lifecycleの状態遷移図
    resumed
    inactive
    hidden
    paused
    detached
    onInactive
    onHide
    onPause
    onDetach
    onResume
    onShow
    onRestart
    Foreground状態
    Kill状態
    Background状態
    ---------------------
    ---------------------

    View Slide

  57. %
    • ライフサイクルイベント発火パターンが3.13.0の前後で異なる
    • OSによってイベント、発火タイミングが異なる
    55
    実際の挙動

    View Slide

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

    View Slide

  59. %
    ~3.13.0
    inactive
    paused
    アプリからOSホームへの遷移
    57
    3.13.0~
    inactive
    hidden
    paused
    J04

    View Slide

  60. %
    ~3.13.0
    resumed
    OSホームからアプリへの遷移
    58
    3.13.0~
    hidden
    inactive
    resumed
    J04

    View Slide

  61. %
    ~3.13.0
    inactive
    paused
    (その後OSホーム、他アプリにいっても変化なし)
    アプリからタスク一覧への遷移
    59
    3.13.0~
    inactive
    hidden
    paused
    (その後OSホーム、他アプリにいっても変化なし)

    View Slide

  62. %
    ~3.13.0
    inactive
    (その後OSホーム、他アプリに遷移した場合)
    paused
    アプリからタスク一覧への遷移
    60
    3.13.0~
    inactive
    (その後OSホーム、他アプリに遷移した場合)
    hidden
    paused
    J04

    View Slide

  63. %
    ~3.13.0
    resumed
    タスク一覧からアプリへの遷移
    61
    3.13.0~
    hidden
    inactive
    resumed

    View Slide

  64. %
    ~3.13.0
    • アプリ=>タスク一覧=>アプリ
    resumed
    • OSホーム=>タスク一覧=>アプリ
    inactive
    resumed
    タスク一覧からアプリへの遷移
    62
    3.13.0~
    • アプリ=>タスク一覧=>アプリ
    resumed
    • OSホーム=>タスク一覧=>アプリ
    hidden
    inactive
    resumed
    J04

    View Slide

  65. %
    ~3.13.0
    inactive
    paused
    画面OFF
    63
    3.13.0~
    inactive
    hidden
    paused
    J04

    View Slide

  66. %
    ~3.13.0
    resumed
    画面ON
    64
    3.13.0~
    hidden
    inactive
    resumed
    J04

    View Slide

  67. %
    ~3.13.0
    イベントなし
    通知欄を表示
    65
    3.13.0~
    inactive

    View Slide

  68. %
    ~3.13.0
    イベントなし
    通知欄を非表示
    66
    3.13.0~
    resumed

    View Slide

  69. %
    ~3.13.0
    • 開く
    inactive
    • 閉じる
    resumed
    通知欄, 設定
    67
    3.13.0~
    • 開く
    inactive
    • 閉じる
    resumed
    J04

    View Slide

  70. %
    ~3.13.0
    inactive
    paused
    detached
    アプリ終了
    68
    3.13.0~
    inactive
    hidden
    paused
    detached
    J04

    View Slide

  71. %
    ~3.13.0
    (inactive)
    (paused)
    resumed
    inactive
    resumed
    画面分割 (Android)
    69
    3.13.0~
    (inactive)
    (hidden)
    (paused)
    hidden
    inactive
    resumed

    View Slide

  72. %
    ~3.13.0
    inactive
    resumed
    画面分割 (iPad)
    70
    3.13.0~
    inactive
    resumed
    J1BE

    View Slide

  73. %
    ~3.13.0
    変化なし
    画面分割時のフォーカス切り替え
    71
    3.13.0~
    • unfocused
    inactive
    • focused
    resumed

    View Slide

  74. %
    ~3.13.0
    (変化なし)
    画面分割時のフォーカス切り替え
    72
    3.13.0~
    (変化なし)
    J1BE

    View Slide

  75. %
    ~3.13.0
    inactive
    paused
    自分のアプリ側の画面分割を終了
    73
    3.13.0~
    inactive
    hidden
    paused

    View Slide

  76. %
    ~3.13.0
    inactive
    paused
    自分のアプリ側の画面分割を終了
    74
    3.13.0~
    inactive
    hidden
    paused
    J1BE

    View Slide

  77. %
    ~3.13.0
    フォーカスに関係なく変化なし
    もう一方のアプリ側の画面分割を終了
    75
    3.13.0~
    • unfocused
    resumed
    • focused
    変化なし

    View Slide

  78. %
    ~3.13.0
    inactive
    resumed
    もう一方のアプリ側の画面分割を終了
    76
    3.13.0~
    inactive
    resumed
    J1BE

    View Slide

  79. %
    3.13.0~
    • Focus
    resumed
    • Unfocus
    inactive
    フォーカス切り替え
    77
    NBD04

    View Slide

  80. %
    3.13.0~
    • 覆う
    hidden
    • 表示
    inactive
    画面を覆う
    78
    NBD04

    View Slide

  81. %
    3.13.0~
    • 画面OFF
    inactive
    hidden
    • 画面ON
    inactive
    resumed
    画面ON/OFF
    79
    NBD04

    View Slide

  82. %
    3.13.0~
    inactive
    hidden
    最小化
    80
    NBD04

    View Slide

  83. %
    3.13.0~
    inactive
    resumed
    最小化からフォーカス
    81
    NBD04

    View Slide

  84. %
    3.13.0~
    inactive
    hidden
    inactive
    resumed
    inactive
    ... (何度かイベント発火)
    resumed
    フルスクリーン化
    82
    NBD04

    View Slide

  85. %
    3.13.0~
    inactive
    hidden
    inactive
    resumed
    inactive
    hidden
    inactive
    resumed
    フルスクリーン解除
    83
    NBD04

    View Slide

  86. %
    ~3.13.0
    イベントなし
    WEB
    84
    3.13.0~
    イベントなし

    View Slide

  87. %
    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

    View Slide

  88. %
    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

    View Slide

  89. %
    • 今まではWidgetsBindingObserverをWidgetに対して実装する必要があった
    • 3.13.0からAppLifecycleListenerが追加
    87
    AppLifecycleListener

    View Slide

  90. %
    88
    class ExamplePage extends StatefulWidget {
    const ExamplePage({super.key});
    @override
    State createState() => _ExamplePageState();
    }
    class _ExamplePageState extends State 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

    View Slide

  91. %
    89
    const ExamplePage({super.key});
    @override
    State createState() => _ExamplePageState();
    }
    class _ExamplePageState extends State 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();

    View Slide

  92. %
    90
    const ExamplePage({super.key});
    @override
    State createState() => _ExamplePageState();
    }
    class _ExamplePageState extends State 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();

    View Slide

  93. %
    91
    const ExamplePage({super.key});
    @override
    State createState() => _ExamplePageState();
    }
    class _ExamplePageState extends State 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();

    View Slide

  94. %
    92
    const ExamplePage({super.key});
    @override
    State createState() => _ExamplePageState();
    }
    class _ExamplePageState extends State 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();

    View Slide

  95. %
    93
    class ExamplePage extends StatefulWidget {
    const ExamplePage({super.key});
    @override
    State createState() => _ExamplePageState();
    }
    class _ExamplePageState extends State {
    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

    View Slide

  96. %
    94
    class ExamplePage extends StatefulWidget {
    const ExamplePage({super.key});
    @override
    State createState() => _ExamplePageState();
    }
    class _ExamplePageState extends State {
    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()),
    );
    }

    View Slide

  97. %
    95
    class ExamplePage extends StatefulWidget {
    const ExamplePage({super.key});
    @override
    State createState() => _ExamplePageState();
    }
    class _ExamplePageState extends State {
    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()),
    );
    }

    View Slide

  98. %
    96
    class ExamplePage extends StatefulWidget {
    const ExamplePage({super.key});
    @override
    State createState() => _ExamplePageState();
    }
    class _ExamplePageState extends State {
    late final AppLifecycleListener appLifecycleListener;
    @override
    void initState() {
    super.initState();
    appLifecycleListener = AppLifecycleListener(
    onStateChange: (state) => print(state.toString()),
    );
    }
    @override
    void dispose() {
    appLifecycleListener.dispose();
    super.dispose();
    }
    @override

    View Slide

  99. %
    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();
    }
    }

    View Slide

  100. %
    98
    @override
    State createState() => _ExamplePageState();
    }
    class _ExamplePageState extends State {
    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にはない

    View Slide

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

    View Slide

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

    View Slide

  103. %
    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

    View Slide

  104. %
    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

    View Slide

  105. %
    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

    View Slide

  106. %
    • Widgetごとのライフサイクルハンドリング
    • アプリ全体のライフサイクルハンドリング
    • 画面遷移はハンドリングできない
    => Navigator history stackを監視する必要がある
    103
    ここまででできること

    View Slide

  107. %
    • 現在主流のものは主に3パターン
    • Navigator (通称Navigator1.0)
    • Router (通称Navigator2.0)
    • ライブラリーを使う(サードパーティ含む)

    fl
    utter.dev/go_router
    • codeness.ly/auto_route
    • tom.gilder.dev/routemaster
    • etc...
    104
    Navigator

    View Slide

  108. %
    • 現在主流のものは主に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

    View Slide

  109. %
    106
    Page1
    Page2
    Page3
    Page1
    Page2
    Page3
    Page4
    Page1
    Page2
    Page3
    Push Page4 Pop Page4

    View Slide

  110. %
    107
    Page1
    Page2
    Page3
    Page1
    Page2
    Page3
    Page4
    Page1
    Page2
    Page3
    Navigator Router
    (GoRouter etc...)
    Widget

    View Slide

  111. %
    • Navigator history stackの変化を監視
    • didPush
    • didPop
    • didRemove
    • didReplace
    • (didStartUserGesture)
    • (didStopUserGesture)
    108
    NavigatorObserver

    View Slide

  112. %
    109
    class NavigatorObserver {
    NavigatorState? get navigator => _navigators[this];
    static final Expando _navigators = Expando();
    void didPush(Route route, Route? previousRoute) { }
    void didPop(Route route, Route? previousRoute) { }
    void didRemove(Route route, Route? previousRoute) { }
    void didReplace({ Route? newRoute, Route? oldRoute }) { }
    void didStartUserGesture(Route route, Route? previousRoute) { }
    void didStopUserGesture() { }
    }

    View Slide

  113. %
    • RouteObserverと組み合わせて利用する
    • 実装されたWidgetのNavigationに変更があると通知される
    • didPopNext
    • didPush
    • didPop
    • didPushNext
    110
    RouteAware

    View Slide

  114. %
    111
    abstract mixin class RouteAware {
    void didPopNext() { }
    void didPush() { }
    void didPop() { }
    void didPushNext() { }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  119. %
    116
    class RouteObserver> extends NavigatorObserver {
    @override
    void didPop(Route route, Route? previousRoute) {
    ...
    }
    @override
    void didPush(Route route, Route? previousRoute) {
    ...
    }
    ...
    }

    View Slide

  120. %
    117
    final RouteObserver routeObserver = RouteObserver();

    View Slide

  121. %
    118
    final RouteObserver routeObserver = RouteObserver();
    Navigator(
    pages: [
    ...
    ],
    onPopPage: (route, result) {
    ...
    }
    );
    MaterialApp(
    routes: {
    ...
    },
    );
    GoRouter(
    initialLocation: '/',
    routes: [
    ...
    ],
    );
    Navigator1.0 Navigator2.0(Router) GoRouter

    View Slide

  122. %
    119
    final RouteObserver routeObserver = RouteObserver();
    Navigator(
    observers: [
    routeObserver,
    ],
    pages: [
    ...
    ],
    onPopPage: (route, result) {
    ...
    }
    );
    MaterialApp(
    navigatorObservers: [
    routeObserver,
    ],
    routes: {
    ...
    },
    );
    GoRouter(
    observers: [
    routeObserver,
    ],
    initialLocation: '/',
    routes: [
    ...
    ],
    );
    Navigator1.0 GoRouter
    Navigator2.0(Router)

    View Slide

  123. %
    120
    class ExamplePage extends StatefulWidget {
    const ExamplePage({ super.key });
    @override
    State createState()
    => _ExamplePageState();
    }
    class _ExamplePageState extends State
    with RouteAware {
    @override
    void didChangeDependencies() {
    super.didChangeDependencies();
    routeObserver.subscribe(
    this,
    ModalRoute.of(context)! as PageRoute,
    );
    }
    @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();
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  126. %
    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
    with RouteAware {
    pendencies() {
    eDependencies();
    subscribe(
    f(context)! as PageRoute,

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  132. %
    128
    Handling Flutter lifecycle
    • 従来のAndroid, iOSのようなライフサイクルイベントは
    Flutterには用意されていない
    • Android: onResume, onPause, etc.
    iOS: viewDidAppear, viewDidDesappear, etc.
    • 画面遷移
    • 端末の画面ON/OFF
    • アプリ自体のバックグラウンドへの移行イベント
    Handling lifecycle
    AppLifecycleListener
    (WidgetBindingObserver)と
    NavigationObserverを
    組み合わせる必要がある
    OK
    CANCEL

    View Slide

  133. %
    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);

    View Slide

  134. %
    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)';
    }

    View Slide

  135. %
    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などのイベントを
    判別するため

    View Slide

  136. %
    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);

    View Slide

  137. %
    133
    class AppLifecycleObserver with RouteAware {
    AppLifecycleObserver({
    required this.routeObserver,
    required ModalRoute 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));
    }
    }

    View Slide

  138. %
    134
    class AppLifecycleObserver with RouteAware {
    AppLifecycleObserver({
    required this.routeObserver,
    required ModalRoute 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() {

    View Slide

  139. %
    class AppLifecycleObserver with RouteAware {
    AppLifecycleObserver({
    required this.routeObserver,
    required ModalRoute 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

    View Slide

  140. %
    class AppLifecycleObserver with RouteAware {
    AppLifecycleObserver({
    required this.routeObserver,
    required ModalRoute 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

    View Slide

  141. %
    class AppLifecycleObserver with RouteAware {
    AppLifecycleObserver({
    required this.routeObserver,
    required ModalRoute 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

    View Slide

  142. %
    class AppLifecycleObserver with RouteAware {
    AppLifecycleObserver({
    required this.routeObserver,
    required ModalRoute 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

    View Slide

  143. %
    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));
    }
    }

    View Slide

  144. %
    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

    View Slide

  145. %
    141
    final RouteObserver routeObserver = RouteObserver();
    Navigator(
    observers: [
    routeObserver,
    ],
    pages: [
    ...
    ],
    onPopPage: (route, result) {
    ...
    }
    );
    MaterialApp(
    navigatorObservers: [
    routeObserver,
    ],
    routes: {
    ...
    },
    );
    GoRouter(
    observers: [
    routeObserver,
    ],
    initialLocation: '/',
    routes: [
    ...
    ],
    );
    Navigator1.0 GoRouter
    Navigator2.0(Router)
    利用側

    View Slide

  146. %
    142
    利用側
    class Page extends StatefulWidget {
    const Page({super.key});
    @override
    State createState() =>
    _PageState();
    }
    class _PageState extends State {
    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();
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  149. %
    • 問題点
    • 各Widgetでそれぞれ書くのは冗長
    • Contextが必要
    145
    Handling Flutter lifecycle

    View Slide

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

    View Slide

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

    View Slide

  152. %
    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> _subscribers =
    >{};
    final List> _routeStack = [];
    @override
    void didPop(Route route, Route? previousRoute) {
    _routeStack.remove(route);
    _emitLifecycleEvent(route, const LifecycleEventPaused(false));
    _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false));
    }
    @override
    void didPush(Route route, Route? previousRoute) {
    _routeStack.add(route);
    _emitLifecycleEvent(previousRoute, const LifecycleEventPaused(false));
    _emitLifecycleEvent(route, const LifecycleEventResumed(false));
    }
    @override
    void didRemove(Route route, Route? previousRoute) {
    if (_routeStack.last == route) {
    _emitLifecycleEvent(route, const LifecycleEventPaused(false));
    _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false));
    }
    _routeStack.remove(route);
    }
    @override
    void didReplace({Route? newRoute, Route? 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 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? 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);
    }
    }
    }

    View Slide

  153. %
    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> _subscribers =
    >{};
    final List> _routeStack = [];
    @override
    void didPop(Route route, Route? previousRoute) {
    _routeStack.remove(route);
    @over
    void
    fin
    if
    _
    }
    _ro
    if
    _
    }
    if
    _
    }
    }
    void
    _ap
    _su
    }
    void
    fin

    View Slide

  154. %
    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> _subscribers =
    >{};
    final List> _routeStack = [];
    @override
    void didPop(Route route, Route? previousRoute) {
    _routeStack.remove(route);
    @over
    void
    fin
    if
    _
    }
    _ro
    if
    _
    }
    if
    _
    }
    }
    void
    _ap
    _su
    }
    void
    fin
    Navigation stack historyを管理

    View Slide

  155. %
    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> _subscribers =
    >{};
    final List> _routeStack = [];
    @override
    void didPop(Route route, Route? previousRoute) {
    _routeStack.remove(route);
    @over
    void
    fin
    if
    _
    }
    _ro
    if
    _
    }
    if
    _
    }
    }
    void
    _ap
    _su
    }
    void
    fin
    StringのKeyでListenerの管理
    Keyには route.nameを使う

    View Slide

  156. %
    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> _subscribers =
    >{};
    final List> _routeStack = [];
    @override
    void didPop(Route route, Route? previousRoute) {
    _routeStack.remove(route);
    @over
    void
    fin
    if
    _
    }
    _ro
    if
    _
    }
    if
    _
    }
    }
    void
    _ap
    _su
    }
    void
    fin
    AppLifecycleListenerは同じ

    View Slide

  157. %
    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> _subscribers =
    >{};
    final List> _routeStack = [];
    @override
    void didPop(Route route, Route? previousRoute) {
    _routeStack.remove(route);
    @over
    void
    fin
    if
    _
    }
    _ro
    if
    _
    }
    if
    _
    }
    }
    void
    _ap
    _su
    }
    void
    fin
    Navigation route の一番上にあるRouteに通知

    View Slide

  158. %
    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? 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);
    }
    }
    }

    View Slide

  159. %
    155
    @override
    void didPop(Route route, Route? previousRoute) {
    _routeStack.remove(route);
    _emitLifecycleEvent(route, const LifecycleEventPaused(false));
    _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false));
    }
    @override
    void didPush(Route route, Route? previousRoute) {
    _routeStack.add(route);
    _emitLifecycleEvent(previousRoute, const LifecycleEventPaused(false));
    _emitLifecycleEvent(route, const LifecycleEventResumed(false));
    }
    @override
    void didRemove(Route route, Route? 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

    View Slide

  160. %
    156
    @override
    void didPop(Route route, Route? previousRoute) {
    _routeStack.remove(route);
    _emitLifecycleEvent(route, const LifecycleEventPaused(false));
    _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false));
    }
    @override
    void didPush(Route route, Route? previousRoute) {
    _routeStack.add(route);
    _emitLifecycleEvent(previousRoute, const LifecycleEventPaused(false));
    _emitLifecycleEvent(route, const LifecycleEventResumed(false));
    }
    @override
    void didRemove(Route route, Route? 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

    View Slide

  161. %
    157
    @override
    void didPop(Route route, Route? previousRoute) {
    _routeStack.remove(route);
    _emitLifecycleEvent(route, const LifecycleEventPaused(false));
    _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false));
    }
    @override
    void didPush(Route route, Route? previousRoute) {
    _routeStack.add(route);
    _emitLifecycleEvent(previousRoute, const LifecycleEventPaused(false));
    _emitLifecycleEvent(route, const LifecycleEventResumed(false));
    }
    @override
    void didRemove(Route route, Route? 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

    View Slide

  162. %
    158
    true));
    rue));
    @override
    void didReplace({Route? newRoute, Route? 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 subscribers =
    _subscribers.putIfAbsent(key, () => {});
    subscribers.add(callback);

    View Slide

  163. %
    159
    true));
    rue));
    @override
    void didReplace({Route? newRoute, Route? 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 subscribers =
    _subscribers.putIfAbsent(key, () => {});
    subscribers.add(callback);
    Page1
    Page2
    Page3
    Page1
    Page4
    Page3
    Page1
    Page2
    Page3
    Page1
    Page2
    Page4

    View Slide

  164. %
    160
    true));
    rue));
    @override
    void didReplace({Route? newRoute, Route? 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 subscribers =
    _subscribers.putIfAbsent(key, () => {});
    subscribers.add(callback);

    View Slide

  165. %
    161
    ;
    if (_routeStack.last == newRoute) {
    _emitLifecycleEvent(newRoute, const LifecycleEventResumed(false));
    }
    }
    void dispose() {
    _appLifecycleListener.dispose();
    _subscribers.clear();
    }
    void subscribe(String key, LifecycleObserverCallback callback) {
    final Set 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? route, LifecycleEvent event) {
    final key = route?.settings.name;
    if (key == null) {

    View Slide

  166. %
    162
    ;
    if (_routeStack.last == newRoute) {
    _emitLifecycleEvent(newRoute, const LifecycleEventResumed(false));
    }
    }
    void dispose() {
    _appLifecycleListener.dispose();
    _subscribers.clear();
    }
    void subscribe(String key, LifecycleObserverCallback callback) {
    final Set 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? route, LifecycleEvent event) {
    final key = route?.settings.name;
    if (key == null) {

    View Slide

  167. %
    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 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);
    すぐに通知されてしまうので、
    適宜フラグで送らないようにするのも良い

    View Slide

  168. %
    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(),
    );
    },
    ),
    ],
    );
    利用側

    View Slide

  169. %
    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をセット

    View Slide

  170. %
    166
    利用側
    class _PageState extends State {
    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();
    }
    }

    View Slide

  171. %
    167
    利用側
    class _PageState extends State {
    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();
    }
    }

    View Slide

  172. %
    168
    利用側
    class _PageState extends State {
    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でも呼べる

    View Slide

  173. %
    おまけ
    169

    View Slide

  174. %
    170
    Riverpodで使うなら

    View Slide

  175. %
    171
    @Riverpod(keepAlive: true)
    AppLifecycleObserver appLifecycleObserver(
    AppLifecycleObserverRef ref,
    )
    => AppLifecycleObserver();

    View Slide

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

    View Slide

  177. %
    Stream appLifecycleStream(
    AppLifecycleStreamRef ref,
    String key,
    ) async* {
    StreamController 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は内部で参照

    View Slide

  178. %
    174
    Stream appLifecycleStream(
    AppLifecycleStreamRef ref,
    String key,
    ) async* {
    StreamController 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

    View Slide

  179. %
    175
    Stream appLifecycleStream(
    AppLifecycleStreamRef ref,
    String key,
    ) async* {
    StreamController 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

    View Slide

  180. %
    176
    利用側
    final event = ref.watch(appLifecycleStreamProvider('page1'));
    switch(event) {
    case AsyncData(:final value):
    print(value);
    }

    View Slide

  181. %
    177
    goRouter.routerDelegate.currentConfiguration.routes;
    GoRouter内で管理しているNavigation historyを取得できる

    View Slide

  182. %
    178
    @override
    void didRemove(Route route, Route? previousRoute) {
    if (_routeStack.last == route) {
    _emitLifecycleEvent(route, const LifecycleEventPaused(false));
    _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false));
    }
    _routeStack.remove(route);
    }
    @override
    void didRemove(Route route, Route? 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を参照

    View Slide

  183. %
    179
    @override
    void didRemove(Route route, Route? previousRoute) {
    if (_routeStack.last == route) {
    _emitLifecycleEvent(route, const LifecycleEventPaused(false));
    _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false));
    }
    _routeStack.remove(route);
    }
    @override
    void didRemove(Route route, Route? 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を参照

    View Slide

  184. %
    180
    @override
    void didRemove(Route route, Route? previousRoute) {
    if (_routeStack.last == route) {
    _emitLifecycleEvent(route, const LifecycleEventPaused(false));
    _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false));
    }
    _routeStack.remove(route);
    }
    @override
    void didRemove(Route route, Route? 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

    View Slide

  185. %
    181
    @override
    void didRemove(Route route, Route? previousRoute) {
    if (_routeStack.last == route) {
    _emitLifecycleEvent(route, const LifecycleEventPaused(false));
    _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false));
    }
    _routeStack.remove(route);
    }
    @override
    void didRemove(Route route, Route? 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

    View Slide

  186. %
    X
    @override
    void didRemove(Route route, Route? previousRoute) {
    if (_routeStack.last == route) {
    _emitLifecycleEvent(route, const LifecycleEventPaused(false));
    _emitLifecycleEvent(previousRoute, const LifecycleEventResumed(false));
    }
    _routeStack.remove(route);
    }
    @override
    void didRemove(Route route, Route? 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

    View Slide

  187. %
    • Widget, App, Navigationそれぞれのライフサイクルを学んだ
    • Flutter3.13.0からイベントの発火タイミングが変わったので注意が必要
    • FlutterはEverything is a widgetになっているため、
    画面遷移のライフサイクルイベントはNavigationを考慮する必要がある
    • それぞれを組み合わせて初めてFlutterのライフサイクルにあった処理ができる
    • 今日のサンプルコード
    https://github.com/AAkira/
    fl
    utter_lifecycle_example
    182
    Summary

    View Slide

  188. Thank you!
    @_a_akira
    We are hiring!
    https://jobs.m3.com/engineer/

    View Slide