くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Flutterを再開するときにやったこと。その2(登場人物とRiverpodを思い出す)

Flutter再開しようと思ったときにやったことのメモ。作業ログ的な備忘録。
インストールまでは終わったので、いろいろ思い出す。

とりあえず、過去に書いた記事を読み直す

Flutterのタグをつけていた記事を見直す。

この記事が、わりとちゃんと知りたいことをまとめてあった、えらい(´ω`)

Dartの特徴、ディレクトリ構成、UI関連(StatelessWidget/StatefulWidget)など思い出してきた。

lib配下のディレクトリ構成を決める

とりあえず、ディレクトリ構成を決める。
前に考えたシンプルな構成をベースに拡張。

lib/
├── configs     ... 共通の設定関連
├── domain      ... コアの処理関連
│   └── models  ... モデル関連
├── pages       ... ページなどのUI関連
│   └── widgets ... ページ内で使うWidget
└── main.dart   ... メイン。テーマのカスタムやルーティングも

必要なライブラリを入れる

Riverpod

とりあえず、状態管理にRiverpodを入れる。

# pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_hooks: ^0.18.0
  hooks_riverpod: ^2.0.0-dev.5

flutter pub getで変更を反映。

Riverpodの使い方

以前、調べてたときと少し変わっているっぽい。

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: appName,
      theme: appTheme,
      home: const ProviderScope(
        child: MyHomePage(title: 'Flutter Demo Home Page'),
      ),
    );
  }
}

ProviderScopeをつけるのは変わらず。

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

// provider
final homeProvider =
    StateNotifierProvider<HomeState, Counter>((ref) => HomeState());

// state class
class HomeState extends StateNotifier<Counter> {
  HomeState() : super(const Counter());

  void countUp() {
    state = Counter(count: state.count + 1);
  }
}

// state data
@immutable
class Counter {
  const Counter({this.count = 0});
  final int count;
}


class MyHomePage extends HookConsumerWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(homeProvider);
    final provider = ref.read((homeProvider.notifier));

    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Center(
        child: Column(
          children: <Widget>[
            Text('${state.count}'),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: provider.countUp,
        child: const Icon(Icons.add),
      ), 
    );
  }
}

呼び出し方が少し違う感じ。

  • StateNotifierProviderの型が第2引数を取るように
  • StateNotifierが完全にイミュータブルに
  • HookConsumerWidgetConsumerWidgetを継承するように
  • useProviderがなくなり、WidgetRef(ref)を使うように

今の使い方はこのあたり。
プロバイダとは | Riverpod
プロバイダの利用方法 | Riverpod

変更点などは、このあたり。
^0.13.0 → ^0.14.0 | Riverpod

プロバイダの種類

各プロバイダの種類はこんな感じ。

プロバイダの種類 生成されるステートの型 具体例
Provider 任意 サービスクラス / 算出プロパティ(リストのフィルタなど)
StateProvider 任意 フィルタの条件 / シンプルなステートオブジェクト
FutureProvider 任意のFuture API の呼び出し結果
StreamProvider 任意のStream API の呼び出し結果のStream
StateNotifierProvider StateNotifierのサブクラス イミュータブル(インタフェースを介さない限り)で複雑なステートオブジェクト
ChangeNotifierProvider ChangeNotifier のサブクラス ミュータブルで複雑なステートオブジェクト

プロバイダの種類 | プロバイダとは | Riverpod

refを使ったプロバイダを利用方法

使い方は以下の通り。

  • ref.watch
    • プロバイダの値を取得した上で、その変化を監視する。値が変化すると、その値に依存するウィジェットやプロバイダの更新が行われる。
  • ref.listen
    • プロバイダの値を監視し、値が変化するたびに呼び出されるコールバック関数(画面遷移、ダイアログの表示など)を登録する。
  • ref.read
    • プロバイダの値を取得する(監視はしない)。クリックイベント等の発生時に、その時点での値を取得する場合に使用できる。

ステートの値は、ref.watchref.listen。(ref.watch(provider))
ステート自体を取得する場合は、ref.readがよい。(ref.read((provider.notifier)))

watch メソッドは ElevatedButton の onPressed 内など、非同期的な場面で呼び出さないでください。
また initState を始め、State のライフサイクルメソッド内での使用も避けてください。

これらの場合は代わりに ref.read を使用してください。

ref.watch を使ってプロバイダを監視する | プロバイダの利用方法 | Riverpod

flutter_hooks

flutter_hooksに関しては、公式ドキュメントとこの記事がわかりやすかった。

flutter_hooks | Flutter Package
Flutter HooksのuseXXXの使い方 - Qiita

ライフサイクル

ライフサイクルには、アプリのライフサイクル(AppLifecycleState)と
画面のライフサイクル(StatefulWidget)があるよう。

AppLifecycleStateは、WidgetsBindingObserverを使わないと取得できないよう。
StatefulWidgetWidgetのライフサイクルのため、
AndroidのActivityのようなライフサイクルを見たい場合は、アプリのサイクルを見る必要がある。

以上!! だいぶ思い出してきた(´ω`)