こんにちは、CX事業本部 IoT事業部の若槻です。
前回のエントリではFlutterでRadio classをラジオボタンを実装してみました。
しかし同じWidget内にRadio classをそのまま記述すると可読性が悪くなってくるので部品化をしたいですね。
そこで今回は、Flutterアプリの実装で状態管理ライブラリproviderを使いつつRadioボタンを別Widget化して再利用できるようにしてみました。
providerとは
providerはInheritedWidgetクラスのラッパーで、いわゆる状態管理ライブラリです。
InheritedWidgetを使うとWidget Treeでの情報伝搬を効率的に実装できますが、providerを使用するとより簡単に実装をすることができます。
providerはFlutter Favorite programの対象となっており、Flutterアプリケーションを構築する際に最初に導入を検討するべきライブラリとされています。
やってみた
providerをインストールします。
flutter pub add provider
providerおよびRadioクラスを使用して次のように実装します。providerではプロバイダーを使って状態管理を実装します。
lib/main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
//プロバイダーを作成
class CityProvider extends ChangeNotifier {
City value = City.ueno; //状態の初期値
//プロバイダーで管理している状態を更新するメソッド
void changeCity(City newValue) {
value = newValue;
notifyListeners(); //状態更新を伝搬させる
}
}
void main() => runApp(
//プロバイダーの状態管理の範囲を定義
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CityProvider()),
],
child: const MyApp(),
),
);
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const Center(
child: MyStatefulWidget(),
),
),
);
}
}
enum City {
ueno('上野'),
shinjuku('新宿'),
akihabara('秋葉原'),
ikebukuro('池袋'),
shibuya('渋谷');
final String displayName;
const City(this.displayName);
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
@override
Widget build(BuildContext context) {
return Column(
children: const <Widget>[
RadioWidget(radioValue: City.ueno),
RadioWidget(radioValue: City.shinjuku),
RadioWidget(radioValue: City.akihabara),
RadioWidget(radioValue: City.ikebukuro),
RadioWidget(radioValue: City.shibuya),
],
);
}
}
class RadioWidget extends StatefulWidget {
final City radioValue;
const RadioWidget({super.key, required this.radioValue});
@override
State<RadioWidget> createState() => _RadioWidget();
}
class _RadioWidget extends State<RadioWidget> {
@override
Widget build(BuildContext context) {
//context.watchによりプロバイダーの更新を監視し、更新時に反映する
CityProvider cityProvider = context.watch<CityProvider>();
return ListTile(
title: Text(widget.radioValue.displayName),
leading: Radio<City>(
value: widget.radioValue,
groupValue: cityProvider.value,
onChanged: (City? value) {
setState(() {
//プロバイダーの状態を更新する
cityProvider.changeCity(value!);
});
},
),
);
}
}
_RadioWidget
で自身はcontext.watchによりプロバイダーの状態更新を監視し、かつonChanged
で選択変更時にchangeCity()
を実行して状態を更新して、他のWidgetに状態更新を伝搬させます。
アプリを起動すると、ラジオボタンがちゃんと動作していますね。良さそうです。
ハマったこと
Error: Could not find the correct Provider above this RadioWidget Widget
当初、runApp()
でのアプリ実行を次のように実装したところ、アプリ実行時にエラーが発生しました。
lib/main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CityProvider extends ChangeNotifier {
City value = City.ueno;
void changeCity(City newValue) {
value = newValue;
notifyListeners();
}
}
void main() => runApp(const MyApp());
RadioWidget
の上位にProvider<CityProvider>
が見つからないためエラーとなっているようです。
Another exception was thrown: Error: Could not find the correct Provider
above this RadioWidget Widget
次のようにMyApp()
をMultiProvider()
の子要素とする必要がありました。
lib/main.dart
- void main() => runApp(const MyApp());
+ void main() => runApp(
+ MultiProvider(
+ providers: [
+ ChangeNotifierProvider(create: (_) => CityProvider()),
+ ],
+ child: const MyApp(),
+ ),
+ );
以降プロバイダーを追加したい場合はMultiProvider.providers
にプロバイダーを追加していけばOKです。
void main() => runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CityProvider()),
ChangeNotifierProvider(create: (_) => HogeProvider()),
ChangeNotifierProvider(create: (_) => FugaProvider()),
],
child: const MyApp(),
),
);
以上