はじめに
Flutterを網羅的に学習するにあたってRoadmapを使って学習を進めることにしました。
この記事では、Flutter初学者やこれからFlutterを学習し始める方に向けて、Providerについてまとめています。
RoadmapはFlutterだけでなく、他の言語やスキルのロードマップも提供されており、何から学習して良いか分からないと悩んでいる方にとって有用なサイトになっています。
ぜひRoadmapを利用して学習してみてください。
Roadmapとは
簡潔に言えば、Roadmap.shは学習者にとってのガイドブックであり、学習の方向性を提供する学習ロードマップサイトです。
初心者から上級者まで、ステップバイステップでスキルを習得するための情報が提供されています。
学習の進め方が分かりやすく示されているだけでなく、個々の項目に参考資料やリソースへのリンクも提供されているので、学習者は目標を設定し、自分自身のペースで学習を進めることができます。
Provider
FlutterロードマップProviderでは以下の2つのサイトが紹介されています。興味のある方はぜひお読みください。
- provider: https://pub.dev/packages/provider
- Simple app state management: https://docs.flutter.dev/data-and-backend/state-mgmt/simple
Providerとは
Provider とは、Flutterアプリケーションで状態管理を行うためのパッケージです。特に ChangeNotifier などの状態オブジェクトをウィジェットツリーに提供するために利用されます。Provider を使用することで、状態変更を検知してUIを更新するといった作業が簡単になります。
Providerのメリット
-
簡潔なコード構造: UIとデータのバインディングを容易に行うことができるため、状態管理のためのコードが簡潔になります。
-
依存性の注入: アプリケーションの各部分で使用するデータを、依存性の注入を通じて渡すことができるため、特に大規模なアプリケーションでは、データの一貫性を確保しやすくなります。
-
プロバイダーの統一: ChangeNotifierProvider、FutureProvider、StreamProvider など、異なるタイプのプロバイダーを提供しているため、異なるデータのタイプに対して統一的な手法で状態管理を行えます。
-
再利用性の向上: Provider を利用することで、ウィジェットツリー内での再利用が容易になります。データの提供や更新に関するロジックをカプセル化し、再利用可能なコンポーネントを構築できます。
Provider.value() Provider.of()
Widget ツリーの中に Provider.value<T>()
を挟んでおくことで、それ以下の Widget から値を取得できるようになります。Tには共有したいデータの型を指定します。
Provider.of<T>(context)
は Widget ツリー内のどこからでもProviderで提供されたデータにアクセスすることができます。Tには参照したいProviderの型を指定します。
Provider.of(context)
は内部では InheritedWidget
の inheritFromWidgetOfExactType
メソッドを使っているため、 initState
より後でしか呼ぶことができません。
まとめると Provider.value()
でデータを渡し、 Provider.of()
はそのデータを受けとるために使用します。この二つのメソッドを組み合わせることで、Flutterアプリ内で状態を簡単に管理できるようになります。
Provider.value<T>()
の他にもProvider<T>()
を使用する方法があります。
Provider(
create: (_) => UserRepository(),
)
Provider.value(
value: UserRepository(),
)
どちらを選択するかは使用ケースに依存しますが、既にインスタンスがある場合は .value
を使用し、都度新しいインスタンスを生成する必要がある場合は通常のコンストラクタを使用します。
既に生成されたインスタンスを提供するため、.value
の方が再利用性が高く、処理が最小限に抑えられます。
context.read() / context.watch()
context.read()
と context.watch()
は、 Providerパッケージで状態を読み込むためのメソッドです。
-
context.read<T>()
:- Providerの現在の値を読み込むだけです。変更通知を購読しないため、状態変更時に自動的に再ビルドされることはありません。
- 値の変更が関係なく、一度だけデータを取得したい場合に使用します。
- 変更があってもウィジェットは再構築されません。
-
context.watch<T>()
:- Providerの現在の値を読み込み、同時に変更通知を購読するため、状態が変更されるたびにWidgetを再ビルドします。
- 変更通知が発生するとウィジェットが再構築されます。
-
context.read<T>()
との違いは再ビルドするかしないかです。 - ウィジェットの再構築をトリガーするときに、関連するウィジェットのビルドをトリガーすることになりますが、
Provider.of()
は関連するすべてのウィジェットを再構築するため、context.watch()
の方が最適化された再構築が行えます。
MultiProvider
Provider を複数使いたいときに使用します。
MultiProvider
を使用しない場合、個別に Provider
を定義する必要があり、以下のようにネストした Provider
を定義することで、 MyApp
以下のWidgetツリーから UserRepository
と ProductRepository
にアクセスできるようになります。
しかし、この方法ではコードが冗長になり、Providerが増えるたびにコードの可読性が低下します。
MultiProvider
を使用することで、複数の Provider
をまとめて定義することができ、コードはシンプルかつ可読性が高くなるのです。
Provider(
create: (_) => UserRepository(),
child: Provider(
create: (_) => ProductRepository(),
child: MyApp()
)
)
MultiProvider(
providers: [
Provider(create: (_) => UserRepository()),
Provider(create: (_) => ProductRepository()),
],
child: MyApp()
)
主な Provider
名前 | 説明 |
---|---|
Provider | シンプルな値やオブジェクトを提供するための基本的なプロバイダーです。create コンストラクタを使用してインスタンスを生成します |
ChangeNotifierProvider | ChangeNotifier クラスを使用したプロバイダーで、状態が変更されたときに通知され、UIの再構築をトリガーします |
ListenableProvider | Listenable クラスを使用したプロバイダーで、変更をリッスンすることができます。Listenable クラスは notifyListeners メソッドを持つクラスです |
ProxyProvider | 他のプロバイダーの値を基にして新しい値を生成するためのプロバイダーです。変更が検知されたときに、新しい値が計算されます |
StreamProvider | 非同期処理である Stream の結果を提供するためのプロバイダーです。Stream が新しいデータを生成するたびに通知されます |
FutureProvider | 非同期処理である Future の結果を提供するためのプロバイダーです。非同期処理が完了すると、その結果が提供されます |
Provider と ChangeNotifierProvider の違い
基本、ChangeNotifier
を継承したクラスの受け渡しを行う場合は ChangeNotifierProvider
を使い、それ以外のデータを受け渡す時は Provider
を使用します。
- Provider: 単にデータのみ受け渡せれば良い時に使用する、データは更新されない。
- ChangeNotifierProvider: データの受け渡しだけでなくデータの更新も行いたい場合に使用する。
ProxyProvider
ProxyProvider
とは、 provider
パッケージで提供されているプロバイダーの一種です。このプロバイダーは、他のプロバイダーからの変更を受けて新しい値を生成するとき、例えば、ログイン前とログイン後で表示する記事一覧を変えたい場合などに使用されます。
ProxyProvider
を使用すると、新しい値の計算を行う際に他のプロバイダーの値に依存できるため、異なるプロバイダー間でのデータの依存関係を効果的に管理できます。
以下の例では、 ProxyProvider
を使用して DependencyProvider
の変更に基づきCounterModel
を提供しています。初回のみ DependencyProvider
の値から CounterModel
を作成し、それ以降は前回の結果を再利用します。
"Update Dependency" ボタンを押すと、 DependencyProvider
の dependencyValue
が更新され、これに基づいて CounterModel
も再計算されます。
サンプルコード
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CounterModel {
int count;
CounterModel(this.count);
}
class DependencyProvider extends ChangeNotifier {
int dependencyValue = 42;
void updateDependencyValue(int newValue) {
dependencyValue = newValue;
notifyListeners();
}
}
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => DependencyProvider()),
ProxyProvider<DependencyProvider, CounterModel>(
update: (_, dependency, previous) =>
previous ?? CounterModel(dependency.dependencyValue),
),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counterModel = Provider.of<CounterModel>(context);
final dependencyProvider = Provider.of<DependencyProvider>(context);
return Scaffold(
appBar: AppBar(
title: Text('ProxyProvider Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Counter: ${counterModel.count}'),
Text('Dependency: ${dependencyProvider.dependencyValue}'),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
counterModel.count += 1;
},
child: Text('Increment Counter'),
),
ElevatedButton(
onPressed: () {
dependencyProvider.updateDependencyValue(99);
},
child: Text('Update Dependency'),
),
],
),
),
);
}
}
StreamProvider
StreamProvider
は、 provider
パッケージで提供されているプロバイダーの一つで、主に非同期のデータを扱う際に使用されます。
-
非同期データの表示: リアルタイムなアップデートがあるデータなどバックエンドからの非同期データを表示する際に使用されます。
-
リアルタイムなUI更新: Stream がデータを提供すると、そのデータが変更されるたびにUIが自動的に更新されます。
-
非同期データのプロバイダー: Stream からデータを非同期に取得し、それをアプリケーション内の他の部分で利用可能にします。
StreamProviderの主なプロパティ
-
create: Stream を生成するための関数を指定します。この関数は
Stream<T>
を返す必要があります。 -
initialData: Stream がデータを返さない場合に使用される初期データを指定します。
-
catchError: Stream でエラーが発生した場合に使用されるデータを指定します。
-
updateShouldNotify: 新しいデータが古いデータと異なる場合にリビルドするかどうかを制御するためのコールバック関数です。通常は
(previous, next) => true
としておくと、新しいデータが来たときにリビルドが行われます。
サンプルコード
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
MaterialApp(
home: StreamProvider<int>(
create: (_) => counterStream(),
initialData: 0,
catchError: (_, error) => 0,
updateShouldNotify: (prev, next) => true,
child: const MyApp(),
),
),
);
}
Stream<int> counterStream() async* {
int count = 0;
while (true) {
await Future.delayed(const Duration(seconds: 1));
yield count++;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final counter = Provider.of<int>(context);
return Scaffold(
appBar: AppBar(
title: const Text('StreamProvider Example'),
),
body: Center(
child: Text('Counter: $counter'),
),
);
}
}
このアプリケーションは、1秒ごとに増加する値をリアルタイムにUIに表示し、StreamProvider を使用して非同期データを効果的に扱う方法を示しています。
-
counterStream
関数では、1秒ごとに値が増加する非同期ストリームを生成します。async*
キーワードを使用して非同期ジェネレーター関数を宣言しています。 -
await Future.delayed(const Duration(seconds: 1));
で1秒待っています。yield count++;
で現在のカウント値を Stream に送信し、カウントを増やしています。 -
StreamProvider
は、 Stream から非同期データを提供するためのものです。 -
create
パラメータは、非同期データを生成するための関数で、この例ではcounterStream
関数が1秒ごとに増加するカウンターの値を生成しています。 -
initialData
は、 Stream がデータを返さない場合に使用される初期データを指定します。この例では初期値を0に設定しています。 -
catchError
は、 Stream でエラーが発生した場合に使用されるデータを指定します。この例ではエラーが発生した場合に0を返すようにしています。 -
MyApp
ウィジェットでは、Provider.of<int>(context)
を使用して現在のカウンターの値を取得しています。この値はText('Counter: $counter')
で表示されます。
FutureProvider
FutureProvider
は、非同期でデータを生成する Future
を提供するためのプロバイダーです。主に非同期の処理が終了した後にその結果をUIに渡す場合に使用されます。
-
非同期データの提供:
FutureProvider
は非同期でデータを取得するために使用されます。例えば、リクエスト処理やデータベースクエリなどの非同期操作が完了した後に、その結果をUIに提供する場合に活用されます。 -
ローディングの管理: データがまだ取得されていない場合や、取得中にローディング状態を表示する際に利用されます。
FutureProviderの主なプロパティ
-
create: 非同期処理を行う Future を返す関数を指定します。この関数は
Provider
が初めて読み込まれたときに呼び出され、その結果が提供されます。 -
initialData: 非同期処理が完了する前に使用される初期データを指定します。非同期処理が完了すると、この初期データは無視されます。
-
catchError: 非同期処理中にエラーが発生した場合に使用されるデータを指定します。これにより、エラーが発生した場合でもUIが正常に表示されるようになります。
-
updateShouldNotify:
Provider
の値が変更されたときにUIを更新するかどうかを制御します。通常は Future の結果が変化するので、変更を通知するためには(_, __) => true
とします。
サンプルコード
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
MaterialApp(
home: FutureProvider<int>(
create: (_) => fetchData(),
initialData: 0,
catchError: (_, error) => 0,
updateShouldNotify: (_, __) => true,
child: const MyApp(),
),
),
);
}
Future<int> fetchData() async {
await Future.delayed(const Duration(seconds: 2));
return 42;
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final int data = Provider.of<int>(context);
return Scaffold(
appBar: AppBar(
title: const Text('FutureProvider Example'),
),
body: Center(
child: Text('Data from Future: $data'),
),
);
}
}
-
main 関数:
-
FutureProvider
を使用して、非同期データを提供するためのMyApp
をラップしています。 -
create
プロパティは非同期関数fetchData
を指定しています。この関数は2秒後に値42
を返します。 -
initialData
プロパティは非同期処理が完了する前に使用される初期データを指定しますこの例では0を返すように指定しています。 -
catchError
プロパティは非同期処理中にエラーが発生した場合に使用されるデータを指定しますここでは0を返すように指定しています。
-
-
fetchData 関数:
-
fetchData
関数は非同期で2秒待機し、その後に42
を返す単純な非同期関数です。
-
-
MyApp クラス:
-
MyApp クラスは
FutureProvider
から提供された非同期データを受け取り、UIに表示しています。 -
Provider.of<int>(context)
を使用して非同期データにアクセスしています。
-
まとめ
Providerとは
Providerは、Flutterアプリ内で状態を管理するためのライブラリです。特に以下のようなメリットがあります。
- 簡潔なコード構造が実現できる
- データの依存性注入がしやすい
- 統一的な状態管理ができる
- コンポーネントの再利用性が向上する
ウィジェットツリーに状態を提供することで、UIとデータを簡単にバインドできるのが Provider の強みです。
基本的な使い方
Providerには Provider.value()
と Provider.of()
の2つの主要なメソッドがあります。
Provider.value()
でデータを提供し、 Provider.of()
でそのデータにアクセスします。この2つのメソッドを組み合わせることで、状態共有が実現できます。
またデータにアクセスするには別の方法も存在します。それがcontext.read()
とcontext.watch()
です。
データを取得する場合、 context.read()
を使うと状態変更時に再ビルドされません。一方 context.watch()
を使うと、状態変更時にウィジェットツリーを再ビルドします。
便利な機能
-
MultiProvider
を使うと、複数の Provider をまとめて定義できます。これによりコードの可読性が上がります。 -
ProxyProvider
は、既存の Provider から値を生成するカスタムProviderです。状態の変換などに利用できます。 -
StreamProvider
やFutureProvider
を使うと、非同期処理の結果を簡単に状態として扱えます。
Providerは状態管理を抽象化し、簡潔で柔軟なコードを実現してくれます。大規模アプリ開発において重宝するライブラリのため、使い方を覚えることで、開発効率が大きく向上するはずです。
参考資料