Providerとは?
-
全域で状態管理ができるようにしてくれるツール。
-
Flutterでは状態を管理する様々な方法があるが、多様なパッケージの中でユーザーが最も多いパッケージ。
Reactで言えばRedux、Recoilのようなツール
なぜProviderを使うのか?
-
コンポーネント間で状態を共有するためには、2つのコンポーネントを1つの親コンポーネントに包むプロセスが必要だ。
-
上記のような過程は、コンポーネントtreeのdepthが深まれば深まるほど不要なコンポーネントを増加させ、このような問題は性能イシューを呼び起こす恐れがある。
-
Providerは、depthに関係なくグローバルに状態を管理し、どこでもデータを簡単に使用できるようサポートしてくれる。
Provider を使ってみよう
- Providerを使って簡単なcounterを実装してみよう。
Directory
├── lib
├── providers
│ ├── counts.dart
├── widgets
│ ├── buttons.dart
│ ├── counter.dart
└── main.dart
Provider install
flutter pub add provider
providers/counts.dart
-
Provider 生成コード。
-
count
の状態を管理する。 -
Provider は
ChangeNotifier
を相続(多重相続)して生成。 -
画面に更新されたデータを反映させるために
notifyListeners
を使用。
// counts.dart
// Provider 生成
import 'package:flutter/material.dart';
class Counts with ChangeNotifier {
int _count = 0;
int get count => _count;
void add() {
_count++;
notifyListeners();
}
void remove() {
_count--;
notifyListeners();
}
}
main.dart
-
MultiProvider
を使えば複数のProviderを追加して複数の状態を管理することができる。 -
1つの状態だけを管理するときは
ChangeNotifierProvider
を使用する。 -
ChangeNotifierProvider
を通じてcounts.dart
で作成したCounts
を購読する。 -
buttons.dart
counter.dart
ファイルでもProviderを使用できるようにChangeNotifierProvider
で包んだ。
// main.dart
// Provider パッケージと上で生成したProviderインポートする
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_example/providers/counts.dart';
import 'package:provider_example/widgets/buttons.dart';
import 'package:provider_example/widgets/counter.dart';
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counts()),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Provider Test',
home: Home(),
);
}
}
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Provider'),
),
body: ChangeNotifierProvider(
create: (BuildContext context) => Counts(),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Counter(),
Buttons(),
],
),
),
),
);
}
}
widgets/button.dart
-
ボタンを画面に表示するコード
-
main.dart
でButtons
コンポーネントをChangeNotifierProvider
で包み込んでいるため、contextを利用してProviderを使用することができる。 -
context.read<T>()
➔ Tのデータ値を変更するなどのイベント用に使用 -
onPressed: () { context.read<Counts>().add(); }
➔ 該当ボタンをクリックすると、countに+1をさせる. -
onPressed: () { context.read<Counts>().remove(); }
➔ 該当ボタンをクリックすると、countに-1をさせる。
// button.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_example/providers/counts.dart';
class Buttons extends StatelessWidget {
const Buttons({super.key});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
context.read<Counts>().add();
},
child: const Icon(Icons.add)),
const SizedBox(
width: 40,
),
ElevatedButton(
onPressed: () {
context.read<Counts>().remove();
},
child: const Icon(Icons.remove))
],
);
}
}
widgets/counter.dart
-
更新されたデータを表示するコード
-
context.watch<T>()
➔ Tのデータ値を画面に表示する用途で使用
// counter.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_example/providers/counts.dart';
class Counter extends StatelessWidget {
const Counter({super.key});
@override
Widget build(BuildContext context) {
return Text(
context.watch<Counts>().count.toString(),
style: const TextStyle(
fontSize: 20,
),
);
}
}
備考
- Consumer を使ったらもっと効率的なコード作成ができそう。
-
Consumerを使用すると、そのWidgetが依存するデータが変更された場合にのみ、そのWidgetをレンダリングする。 これにより、アプリ全体をレンダリングし直すのではなく、必要な部分だけを更新することでアプリの性能を向上させることができる。
-
Consumerを使用するとコードが読みやすくなる。Consumerは該当Widgetで使用するデータへの依存性を明示的に表現できる。 これにより、コードの可読性を高め、debugを容易にする。
# Consumer example
// provider.dart
class Counter extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
// counter.dart
class CounterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<Counter>(
builder: (context, counter, child) {
return Text(
'Count: ${counter.count}',
);
},
);
}
}