flutterの状態管理には
の3つが主流かと思います。
今回は3つの実装方法を比較し、どの実装方法が良いかを見ていきたいと思います。
まずはプレーンなFlutterで実装することができるStatefulWidgetを使ってDropdownを実装します。 実際のコードがこちらです。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'hooks.dart';
class TestStateful extends StatefulWidget {
const TestStateful({Key? key}) : super(key: key);
State<TestStateful> createState() => _TestStateful();
}
class _TestStateful extends State<TestStateful> {
final _items = ['abc', '123',];//アイテム一覧
var selectValue = 'abc';//初期アイテム
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('stateFulWidgetのDropdownButton'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 150,
height: 50,
child: DropdownButton(
isExpanded: true,
value: selectValue,
items: _items.map((String item) {
return DropdownMenuItem(
value: item,
child: Text(
item,
style: const TextStyle(
fontSize: 30, fontWeight: FontWeight.w600),
));
}).toList(),
onChanged: (String? value) {
setState(() {
selectValue = value!;//値を更新して画面再描画
});
print(selectValue);
}),
),
Text(selectValue),
ElevatedButton(onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => TestHooks()),
);
}, child: const Text('HooksのDropDownへ'))
],
),
),
);
}
}
上記のコードはStatefulWidgetでDropDownで実装したコードです。
まず、_items
にDropdownで選択できるアイテムの一覧を入れます。
その後、selectValue
という変数を初期値を入れて作ります。
そして、DropdownButton
内で_itemsの値を使ったDropdownMenuItem
を生成し、onChanged
内で画面再描画を行うsetState({})
の中でselectValue = value!;
を実行することで更新後の値をUIに反映します。
以上がこのコードの解説です。
一見これでも問題ないようにも思えますが、statefulで実装することにはデメリットが存在します。
1点目はコードが長くなってしまう点です。
この後riverpodとflutter_hooksを使ったコードを見ていただけるとわかると思いますが、classを2つ作る必要があること。再描画するのに毎回setState({})
を使う必要があること。などの理由からコードが冗長になりやすいです。
2点目はsetState({})
に問題があることです。
setState({})
は1つの変数の値を更新したい場合であっても画面の全体を再描画してしまいます。
そのため、更新に時間がかかる恐れや端末への負荷が懸念されます。
実際に再描画されたWidgetはこちらになります。
こちらも後述するriverpodとflutter_hooksでの更新と比較していただけるとわかるのですが、全く関係のないAppBar等まで再描画されており、負荷がかかってしまうことがわかると思います。
次に、状態管理ライブラリであるriverpodを使ってDropdownButtonを実装します。 実際のコードがこちらです。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hookstestproject/stateful.dart';
//StateProviderをグローバルに配置
final StateProvider<String> dropDownValueProvider =
StateProvider((ref) => 'abc');
class TestRiverpodStateProvider extends StatelessWidget {
final _items = [
'abc',
'123',
]; //アイテム一覧
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('StateNotifierのDropdownButton'),
),
body: Center(
child: Consumer(//再描画したい部分のみをConsumerで囲む
builder: (context, ref, _) {
//StateProviderの変更を監視する
final selectedValue = ref.watch(dropDownValueProvider);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 150,
height: 50,
child: DropdownButton(
isExpanded: true,
value: selectedValue,
items: _items.map((String item) {
return DropdownMenuItem(
value: item,
child: Text(
item,
style: const TextStyle(
fontSize: 30, fontWeight: FontWeight.w600),
));
}).toList(),
onChanged: (String? value) {
//StateProviderのStateをDropDownMenuItemのvalueに書き換える
ref.read(dropDownValueProvider.notifier).state =
value!;
print(selectedValue);
})),
Text(selectedValue),
ElevatedButton(
onPressed: () {
Navigator.of(
context,
).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => const TestStateful()),
(route) => false);
},
child: const Text('StateFulWidgetのDropDownへ'),
)
],
);
}),
));
}
}
上記のコードはriverpodのStateProviderを使ってDropDownを実装したコードです。
statefulのコードと違う点はStateProviderをグローバルに定義し、そのstateをselectedValue
という変数に格納している点です。
グローバルに定義されたProviderをUIの中で使うにはConsumer
Widgetで囲んでrefを渡す必要があります(他の方法もありますが今回は割愛)
その後、Consumer内で変数を定義し、stateを格納します。
最後にonChanged(){}
内でselectedValue
のstateを
ref.read(dropDownValueProvider.notifier).state = value!
でDropdownのvalueに書き換えたことを通知します。これによってConsumer内のWidgetが再描画されます。
riverpodを使うメリットについてですが、statefulWidgetの問題点を解消できる点が挙げられます。
classを2つ作る必要がなくなり、setState({})
を必要としないのでコードを短くすることが出来ます。
また、Consumer
内のみが再描画されるので余計な負荷を軽減することが出来ます。
実際に再描画されたWidget数はこちらになります。
最後に、状態管理ライブラリであるflutter_hooksを使ってDropdownButtonを実装します。 実際のコードがこちらです。
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hookstestproject/riverpod_StateNotifier_ver.dart';
class TestHooks extends StatelessWidget {
final _items = ['abc', '123',];//アイテム一覧
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter_HooksのDropdownButton'),
),
body: HookBuilder(//Hooksを使いたいWidgetを囲む
builder: (context) {
final dropDownValue = useState('abc');
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 100,
height: 50,
child: DropdownButton(
isExpanded: true,
value: dropDownValue.value,
items: _items.map((String item) {
return DropdownMenuItem(
value: item,
child: Text(
item,
style: const TextStyle(
fontSize: 30, fontWeight: FontWeight.w600),
));
}).toList(),
onChanged: (String? value) {
dropDownValue.value = value!;//選択したDropDownMenuItemのvalueに書き換える
print(dropDownValue.value);
}),
),
Text(dropDownValue.value),
ElevatedButton(onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => TestRiverpod()),
);
}, child: const Text('riverpodのDropDownへ'),)
],
),
);
}
),
);
}
}
上記のコードはflutter_hooksを使ってDropDownを実装したコードです。
構成はriverpodと似ていますが、Consumer
ではなくHookBuilder`でWidgetを囲みます。
次に、HookBuilder
内でuseState
の変数を作ります。
最後にonChanged(){}
内でdropDownValue
のvalueを
dropDownValue.value = value!;
でDropdownのvalueに書き換えたことを通知します。これによってHookBuilder内のWidgetが再描画されます。
hooksを使うメリットについてですが、基本的にはriverpodと変わらないと思います。
riverpodとの違いはグローバルにProviderを定義する必要が無いため、値の利用がWidget内に限られている場合はコードを短くすることが出来ます。 また、他のファイルで読み込む必要がないような今回のような場合では、グローバルに変数を置かないことで似たような処理を行う際に別のproviderと名前が被ってしまうというようなミスが起こりにくい点もメリットではないかと思います。
hooksもriverpodと同じくHookBuilder
内のみが再描画されるので余計な負荷を軽減することが出来ます。
実際に再描画されたWidget数はこちらになります。
今回はDropdownの3つの実装方法を比較してみました。
riverpodとhooksについてはどちらの方が良いかの判断は難しいですが、少なくともStatefulWidgetは使わない方が良さそうであることがわかりました。
今回の記事は以上になります。 ありがとうございました
https://riverpod.dev/ja/docs/providers/state_provider https://blog.flutteruniv.com/flutter-dropdownbutton/
可茂IT塾ではFlutterインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。
Read More可茂IT塾ではFlutterインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。
Read More