【Flutter】providerのstateへのアクセス
よくある書き方がこれ。
void main() {
// Modify from here...
WidgetsFlutterBinding.ensureInitialized();
runApp(ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: ((context, child) => const App()),
));
}
main()関数はすべてのアプリのスタートとなる関数。
ChangeNotifierProviderを宣言することで、状態管理するクラスをすべてのウィジェットで参照できる。
今回だと、
create: (context) => ApplicationState(),
これが状態管理のクラス。
class ApplicationState extends ChangeNotifier
~~~~~~~~~
notifyListeners();
});
状態管理のクラスはChangeNotifierを継承している。
状態管理クラスは状態を管理するのが仕事。要するに状態の変化を検知して保持するということ。
状態の変化を検知した場合に特定の画面に変化したよーっと伝えたいときがある。
そのときに、notifyListeners();をコールする。
notifyListeners()をコールできるのは、ChangeNotifierクラスを継承しているから。
で元に戻ると、
runApp(ChangeNotifierProvider(
create: (context) => ApplicationState(),
前述の通りこの処理のおかげですべての子のウィジェットで状態を参照することができるお膳立てができたというわけ。
では、子のウィジェットでどのように状態を参照しているかというとこんな感じ
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firebase Meetup'),
),
body: ListView(
children: <Widget>[
Image.asset('assets/codelab.png'),
const SizedBox(height: 8),
const IconAndDetail(Icons.calendar_today, 'October 30'),
const IconAndDetail(Icons.location_city, 'San Francisco'),
// Add from here
Consumer<ApplicationState>(
builder: (context, appState, _) => AuthFunc(
loggedIn: appState.loggedIn,
signOut: () {
FirebaseAuth.instance.signOut();
}),
),
この部分です。
Consumer<ApplicationState>(
builder: (context, appState, _) => AuthFunc(
Consumer<T>はTのクラスの変化をlistenできるウィジェットらしいです。
ApplicationStateをみるよーって宣言しているので、
builderの第二引数にappStateという名前でbuilderに渡すよーって言ってます。
appStateはあくまで引数の名前なので、別にappFanFanとかっていう名前でもなんでもいいです。
大事なことは、Consumer<ApplicationState>
で、ApplicationStateっていうクラスの型だけを宣言しているように見えて実はmain関数でChangeNotifierProviderで宣言したことで実際には実体が見えるようになっているというか、参照するよーって宣言しているようです。(このあたりちょっと自分の解釈入っている)
この記事の本質は、
今までC++とかやっていた私からするとこの書き方で実体が参照できているのがすごい不思議だったので、自分用のメモとして残しておこうと思いました。
ちなみに状態管理クラスは他にもアクセス方法あるらしいのでchatgpt先生の回答おいておきます。
chatgpt先生の回答
はい、Flutterのproviderパッケージでは、提供された状態にアクセスするための複数の方法があります。これらの方法は、使用するシナリオに応じて選択されます。以下に、Consumer<ApplicationState>、context.watch()、およびcontext.read()の違いについて説明します。
- Consumer<ApplicationState>
Consumerウィジェットは、指定されたタイプのオブジェクト(この場合はApplicationState)をリッスンし、そのオブジェクトが変更されるたびにbuilder関数を再実行してウィジェットツリーを再構築します。
Consumerは、ウィジェットの一部だけを条件に応じて更新したい場合に便利です。この方法では、状態が変化すると、そのConsumerウィジェット内のUIだけが更新され、他の部分は影響を受けません。 - context.watch<T>()
context.watch<T>()は、指定されたタイプのオブジェクト(T)をリッスンし、そのオブジェクトが変更されるたびにウィジェットを再構築します。このメソッドはbuildメソッド内で使用されることが多く、リッスンしているオブジェクトに依存するウィジェット全体が更新されることになります。
watchは、ウィジェットが特定の状態に依存しており、その状態が変化するとウィジェット全体を更新する必要がある場合に適しています。 - context.read<T>()
context.read<T>()は、指定されたタイプのオブジェクト(T)にアクセスするために使用されますが、オブジェクトの変更をリッスンしません。つまり、readを使用した場合、オブジェクトが変更されてもウィジェットは再構築されません。
readは、ウィジェットが状態を読み取るだけで、その状態の変化によってウィジェットを更新する必要がないアクション(例えば、ボタンクリックで状態のメソッドを呼び出す場合など)に適しています。
これらの方法は、ウィジェットの再構築の必要性や、状態の変化にどのように反応させたいかに応じて選択されます。ConsumerウィジェットはUIの一部分のみの更新に、watchは依存する状態の変化に伴うウィジェット全体の再構築に、readは状態の読み取りのみに(ウィジェットの再構築なしに)使用されることが一般的です。
Discussion