ウイジェットとRiverpod
拡張性を求められるアプリ開発にChangeNotifierProviderは勧められない、とのこと(公式)。
状態(state)を持たない・変化しないUI
不変(immutable)
build() だけ
軽い
再描画は「親からの再build」でのみ起きる
表示専用
props(引数)だけで決まるUI
class TitleText extends StatelessWidget {
final String text;
const TitleText(this.text, {super.key});
@override
Widget build(BuildContext context) {
return Text(text);
}
}
👉 迷ったらまずこれ
(Flutter公式も推奨)
内部に状態を持つUI
State クラスを持つ
setState() でUI更新
ライフサイクルあり
initState() ← 初期化
dispose() ← 後片付け
didUpdateWidget() など
👉 あなたの認識どおり
「initStateが使える」=StatefulWidget
カウンター
アニメーション
TextEditingController
FocusNode
ScrollController
非同期ロード開始
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
@override
void initState() {
super.initState();
print("初期化");
}
@override
Widget build(BuildContext context) {
return Text('$count');
}
}
あなたの文脈的にここ重要そうです。
(Riverpod)
ref が使える
Providerを読む専用
StatefulWidgetより軽い
👉 「状態はProviderに任せる」思想
Widget build(BuildContext context, WidgetRef ref)
ref.watch() → 監視
ref.read() → 一度読む
ref.listen() → 変更検知
アプリのほとんど(Riverpod使用時)
class CounterText extends ConsumerWidget {
const CounterText({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}
(重要・よく使う)
Stateful + ref
👉 最強クラス
initState使える
ref使える
Provider + Controller
Provider + Animation
Provider + 非同期初期化
class MyPage extends ConsumerStatefulWidget {
const MyPage({super.key});
@override
ConsumerState<MyPage> createState() => _MyPageState();
}
class _MyPageState extends ConsumerState<MyPage> {
@override
void initState() {
super.initState();
ref.read(someProvider); // OK
}
@override
Widget build(BuildContext context) {
return Container();
}
}
「サイズを親に教えられるWidget」
主に AppBar 用
Scaffold.appBar は高さが必要
→ それを返す仕組み
カスタムAppBar
AppBarを関数で返すとき
PreferredSizeWidget myAppBar() {
return AppBar(title: Text('Title'));
}
class MyBar extends StatelessWidget implements PreferredSizeWidget {
@override
Size get preferredSize => Size.fromHeight(80);
@override
Widget build(BuildContext context) {
return AppBar(title: Text('MyBar'));
}
}
👉 エディタ警告の理由
「高さを教えて」
状況
使う
表示だけ
StatelessWidget
内部状態あり
StatefulWidget
Provider読む
ConsumerWidget
Provider + initState
ConsumerStatefulWidget
Provider読む?
├ NO → StatelessWidget
└ YES
initState必要?
├ NO → ConsumerWidget
└ YES → ConsumerStatefulWidget
これは種類が違って:
👉 Widgetの分類ではなく「能力(interface)」
Widgetの種類:Stateless / Stateful / Consumer
能力:PreferredSizeWidget / TickerProvider / etc
つまり:
StatelessWidget + PreferredSizeWidget
のように併用するものです。
◎SliderウイジェットをRiverpodで使用する
・SliderウイジェットはsetStateを使って値を更新しないと反映されない。
・Riverpodでは、useStateを使い値を更新する。
(一部抜粋)
class ElemListPage extends HookConsumerWidget{
const ElemListPage ({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref){
var _AppConfigrProvider = ref.watch(gAppConfigrProvider);
Widget _makeElemItemHeightSliderWidget(){
final _slideExtentvalue = useState<double>(_AppConfigrProvider.ElemItemExtent);
return
Card(
child:
Padding(padding: EdgeInsets.all(2),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(AppLocalizations.of(context)!.elementHeightstring),
Expanded(child: SizedBox()),
Slider(
label: '${_slideHeightvalue.value}',
min: gMinElemItemHeight,
max: gMaxElemItemHeight,
divisions: gElemItemHeightDivision,
value: _slideHeightvalue.value,
onChanged:(inDouble){
_AppConfigrProvider.ElemItemHeight = inDouble;
_slideHeightvalue.value = inDouble;
_AppConfigrProvider.notifyListeners();
},
)
]
),
),
);
}
・hooks_riverpod.dartとflutter_hooks.dartをインポートする。
・HookConsumerWidgetを使用する。
・useStateを宣言する。
final _slideExtentvalue = useState<double>(_AppConfigrProvider.ElemItemExtent);
・値を変更するとSliderが更新される。
_slideHeightvalue.value = inDouble;
◎TextFieldウィジェットをRiverpodで使用する
・HookConsumerWidget では、TextEditingControllerだとうまく動かないため、
useTextEditingControllerを使用する。
(一部抜粋)
class ElemListPage extends HookConsumerWidget{
const ElemListPage ({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref){
var _TSVManagerProvider = ref.watch(gTSVManagerProvider);
final _editingController = useTextEditingController(); // TextEditingController だとうまく動かない
(一部抜粋)
TextField(
controller: _editingController,
decoration:
InputDecoration(
labelText: AppLocalizations.of(context)!.inputtextandaddstring, // テキストを入力してください、のラベル
),
// 表示を更新しないとボタンが更新されない。
// →Stateを更新するコードを入れるようにすれば動くと思う
onChanged: (String inStr) {_TSVManagerProvider.TSVNotifyListners();},
),
(一部抜粋)
// 追加ボタン
Opacity(opacity: (_editingController.text.isEmpty) ? 0.3 : 1.0, // テキストフィールドが空白の時は半透明
child:
FloatingActionButton(
tooltip: AppLocalizations.of(context)!.addelemstring,
child: const Icon(Icons.add),
onPressed: _editingController.text.isEmpty ? null : ()async { //テキストフィールドが空白の時は押せない
_TSVManagerProvider.InsertSelectedElemNonotify(_editingController.text);
_TSVManagerProvider.IsSelectedElem = false;
_editingController.text = "";
_TSVManagerProvider.TSVNotifyListners();
//delay を入れて、jumptoが使えるようにする
await Future.delayed(const Duration(milliseconds: 100));
if (_TSVManagerProvider.IsAddBeforeElement == false) {
_gElemListControler.jumpTo(_gElemListControler.position.maxScrollExtent);
}
},
),
・_TSVManagerProvider.TSVNotifyLisners()は、任意のタイミングでnotifylistners()を呼び出したいときに使用している。
notifylistners()を呼ばないと、_editingController.text.isEmptyもうまくいかないし、FloatingActionButtonの透明化が反映されない。
→stateを更新するコードを書けばよいはず。
riverpod を3.0にバージョンアップしたら、
flutter_riverpod: ^2.5.0
hooks_riverpod: ^2.5.0