Flutter の状態管理:setState / Provider / Riverpod をコードで見比べる
March 26, 2022
サンプルコードのウィジェットの階層構造 #
App
└ Layer1
└ Layer2
├ Layer3A
└ Layer3B
setState #
import 'package:flutter/material.dart';
void main() => runApp(const App());
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(home: Layer1());
}
}
class Layer1 extends StatefulWidget {
const Layer1({Key? key}) : super(key: key);
@override
State<Layer1> createState() => _Layer1State();
}
class _Layer1State extends State<Layer1> {
int _count = 0;
void _increment() => setState(() => _count++);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Current count is $_count')),
body: Layer2(count: _count, increment: _increment),
);
}
}
class Layer2 extends StatelessWidget {
const Layer2({Key? key, required this.count, required this.increment})
: super(key: key);
final int count;
final void Function() increment;
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('I am a counter.'),
Layer3A(count: count),
Layer3B(increment: increment),
],
);
}
}
class Layer3A extends StatelessWidget {
const Layer3A({Key? key, required this.count}) : super(key: key);
final int count;
@override
Widget build(BuildContext context) {
final String res = count % 2 == 0 ? 'Even' : 'Odd';
return Text('Count is $res');
}
}
class Layer3B extends StatelessWidget {
const Layer3B({Key? key, required this.increment}) : super(key: key);
final void Function() increment;
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: increment,
child: const Text('INCREMENT'),
);
}
}
Prop Drilling(バケツリレー)になっています。
Provider #
(以下のコードは provider のバージョン 6 系です)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(const App());
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => Counter(),
child: const MaterialApp(home: Layer1()),
);
}
}
class Layer1 extends StatelessWidget {
const Layer1({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Current count is ${context.watch<Counter>().count}')),
body: const Layer2(),
);
}
}
class Layer2 extends StatelessWidget {
const Layer2({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: const [
Text('I am a counter.'),
Layer3A(),
Layer3B(),
],
);
}
}
class Layer3A extends StatelessWidget {
const Layer3A({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final String res = context.watch<Counter>().count % 2 == 0 ? 'Even' : 'Odd';
return Text('Count is $res');
}
}
class Layer3B extends StatelessWidget {
const Layer3B({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: context.read<Counter>().increment,
child: const Text('INCREMENT'),
);
}
}
ChangeNotifierProvider で囲われている配下で Counter にアクセスできます。これによりプロップスの受け渡しがなくなりました。
Provider から値を取得する部分は Consumer を使ってより簡潔に記載することもできます。
See also: #
- provider | Flutter Package
- rrousselGit/provider | GitHub
- rrousselGit/provider - example | GitHub
Riverpod #
(以下のコードは flutter_riverpod のバージョン 1 系です)
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() => runApp(const ProviderScope(child: App()));
final counterProvider = StateProvider<int>((ref) => 0);
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(home: Layer1());
}
}
class Layer1 extends ConsumerWidget {
const Layer1({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar:
AppBar(title: Text('Current count is ${ref.watch(counterProvider)}')),
body: const Layer2(),
);
}
}
class Layer2 extends StatelessWidget {
const Layer2({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: const [
Text('I am a counter.'),
Layer3A(),
Layer3B(),
],
);
}
}
class Layer3A extends ConsumerWidget {
const Layer3A({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final String res = ref.watch(counterProvider) % 2 == 0 ? 'Even' : 'Odd';
return Text('Count is $res');
}
}
class Layer3B extends ConsumerWidget {
const Layer3B({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return TextButton(
onPressed: () {
ref.read(counterProvider.notifier).update((state) => state + 1);
},
child: const Text('INCREMENT'),
);
}
}
ステートを参照するウィジェットは StatelessWidget を継承するのではなく ConsumerWidget を継承するようになりました。
ProviderScope の配下の ConsumerWidget は ref で counterProvider にアクセスできます。
See also: #
- flutter_riverpod | Flutter Package
- rrousselGit/river_pod | GitHub
- rrousselGit/river_pod - example | GitHub
- Riverpod Document
React 的に捉えると #
- Flutter: setState = React: useState
- Flutter: Provider = React: useContext
- Flutter: Riverpod = React: Recoil
に似ている気がします。