1. 概要
上記記事ではSqliteを使い、データを保存しながら状態を管理する内容を書きました。今回はStatelessWidgetを使った場合の状態管理について書きます。
※今回は下記3つの例で、すべて同じ結果になるように構成してます。
- setState()
- StatefulWidget
- build()を呼び出して状態を変化させる
- StatefulWidget
- Provider
- StatelessWidget
- アプリ起動中に状態が変化しない
- ChangeNotifierProvider / ChangeNotifier
- StatelessWidget
- Riverpod
- StatelessWidget / ConsumerWidget
- アプリ起動中に状態が変化しない
- ProviderScope / StateProvider
- StatelessWidget / ConsumerWidget
対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。
2. setState
2-1. プロジェクトを作成
2-2. ソースコード
- main.dart
// ignore_for_file: avoid_print
import 'package:flutter/material.dart';
import 'package:stateful_demo/views/home.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
- views/home.dart
// ignore_for_file: avoid_print
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
ElevatedButton(onPressed: plus, child: const Text('+')),
ElevatedButton(onPressed: minus, child: const Text('-')),
],
),
),
);
}
void plus() {
setState(() {
_counter++;
});
print('plus=$_counter');
}
void minus() {
setState(() {
_counter--;
});
print('minus=$_counter');
}
}
2-3. The Stateful Widget Lifecycle
2-4. 結果

2-5. 解説
状態が変更される対象を「setState()」内に書く事で再び「build()」が呼ばれ、再描画されるため下記が一致してます。
- ログの数値
- 画面の数値
3. Provider
3-1. プロジェクトを作成
3-2. Dependencyのインストール
flutter pub add provider
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
provider: ^6.0.3
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
3-3. ソースコード
- main.dart
import 'package:flutter/material.dart';
import 'package:provider_demo/views/parent.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: ParentWidget(),
);
}
}
- views/parent.dart
import 'package:flutter/material.dart';
import 'package:provider_demo/models/counter.dart';
import 'package:provider/provider.dart';
import 'package:provider_demo/views/child.dart';
class ParentWidget extends StatelessWidget {
final data = CounterData();
ParentWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Provider Demo'),
),
body: ChangeNotifierProvider<CounterData>.value(
value: data,
child: const ChildWidget(),
),
);
}
}
- views/child.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_demo/models/counter.dart';
class ChildWidget extends StatelessWidget {
const ChildWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final CounterData data = context.watch<CounterData>();
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${data.counter}',
style: Theme.of(context).textTheme.headline4,
),
ElevatedButton(
onPressed: () {
data.plus();
},
child: const Text('+')),
ElevatedButton(
onPressed: () {
data.minus();
},
child: const Text('-')),
]),
),
);
}
}
- models/counter.dart
// ignore_for_file: avoid_print
import 'package:flutter/material.dart';
class CounterData extends ChangeNotifier {
int counter = 0;
void plus() {
counter++;
print('plus=$counter');
notifyListeners();
}
void minus() {
counter--;
print('minus=$counter');
notifyListeners();
}
}
3-4. 結果

3-5. 解説
状態管理する所で「ChangeNotifierProvider」を使い、状態管理の対象となるものを渡します。
状態管理の対象となるものは「ChangeNotifier」を継承し、状態の変更があった際に「notifyListeners()」を使い、監視元に知らせます。
状態管理の対象を受け取り、操作します。
- context.watch<T>():値の取得とその変化を監視
- context.read<T>():値の取得のみ(監視はしない)
4. Riverpod
4-1. プロジェクトを作成
4-2. Dependencyのインストール
flutter pub add flutter_riverpod
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
flutter_riverpod: ^1.0.4
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
4-3. ソースコード
- main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_demo/views/home.dart';
final counterProvider = StateProvider((ref) => 0);
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// ignore: prefer_const_constructors
return MaterialApp(
home: const MyHomePage(),
);
}
}
- views/home.dart
// ignore_for_file: avoid_print
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_demo/main.dart';
class MyHomePage extends ConsumerWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('Riverpod example')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Center(
child: Text(
'${ref.watch(counterProvider.state).state}',
style: Theme.of(context).textTheme.headline4,
)),
ElevatedButton(onPressed: () => plus(ref), child: const Text('+')),
ElevatedButton(onPressed: () => minus(ref), child: const Text('-')),
],
),
);
}
void plus(WidgetRef ref) {
ref.read(counterProvider.state).state++;
print('plus=${ref.read(counterProvider.state).state}');
}
void minus(WidgetRef ref) {
ref.read(counterProvider.state).state--;
print('minus=${ref.read(counterProvider.state).state}');
}
}
4-4. 結果

4-5. 解説
「ProviderScope」を使い、状態管理の対象を指定します。
状態を管理するためのプロバイダとして「StateProvider」を使います。
プロバイダを利用するためには「ref」を取得する必要があるので「ConsumerWidget」を継承します。
- プロバイダの利用方法 | Riverpod
- ref.watch():プロバイダの値を取得した上で、その変化を監視する。値が変化すると、その値に依存するウィジェットやプロバイダの更新が行われる。
- ref.read():プロバイダの値を取得する(監視はしない)。クリックイベント等の発生時に、その時点での値を取得する場合に使用できる。
5. 参考
投稿者プロフィール

-
開発好きなシステムエンジニアです。
卓球にハマってます。