はじめに
久しぶりの投稿となります。うらさくです。
この記事はTechCommit AdventCalendar2022の7日目の記事です。
昨日はにしやまさんのGoでdiffコマンド書いて実際の実装と比べてみたでした
概要
Flutterアプリ開発中にいくつかメモしておきたいTipsがあったので、こちらを今回のアドカレのネタにしようと思います。
今回の題材はTextEditingControllerです。
TextEditingControllerは、Flutterのテキスト入力ウィジェット(例えばTextFieldやTextFormField)で使用される、テキスト入力を管理するためのクラスです。
これをRiverpod+freezedの構成で使用する際に、どこでTextEditingControllerを管理するかで少し迷ったので、備忘録として記事に収めたいと思います
構成
下記のようなテキストボックスとテキストボタンのシンプルな構成で考えていきます
class SamplePage extends ConsumerWidget {
const SampleView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final sample = ref.watch(sampleNotifierProvider.notifier);
return Scaffold(
appBar: AppBar(
title: const Text("サンプルアプリ"),
),
body: Column(
children: [
TextField(
maxLines: 3,
minLines: 1,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
),
),
),
TextButton(
onPressed: () {sample.submit()},
child: const Text('登録'),
),
],
),
);
}
}
テキストの状態をfreezedクラスとして保持しており、それをStateNotifierProviderで管理しています
@freezed
class SamplePageState with _$SamplePageState {
const factory SamplePageState({
@Default("") String text,
}) = _SamplePageState;
}
Notifierには登録処理を持たせており、ボタン押下時にstateのtextの登録処理を行います
final SamplePageStateProvider =
StateNotifierProvider.autoDispose<SamplePageNotifier, SamplePageState>((ref) => SamplePageNotifier(ref)
);
class SamplePageNotifier extends StateNotifier<SamplePageState> {
final Ref ref;
SamplePageNotifier(this.ref) : super(SamplePageState());
// 登録する
void submit() {
// 登録処理
}
}
このTextFieldで使うTextEditingControllerをどこで管理すれば良いのかを考えていきます
①Viewで管理する
StatefulWidgetと同じように、ViewでTextEditingControllerを管理することも可能です
class SampleView extends ConsumerWidget {
const SampleView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final sample = ref.watch(sampleNotifierProvider.notifier);
+ TextEditingController _controller = TextEditingController();
return Scaffold(
appBar: AppBar(
title: const Text("サンプルアプリ"),
),
body: Column(
children: [
TextField(
+ controller: _controller,
maxLines: 3,
minLines: 1,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
),
),
),
以下略
ただViewで管理してしまうと、stateのtextと実際の入力値が即座にリンクされないため、登録処理の際にviewからtextをnotifierに渡してあげる必要があります
TextButton(
- onPressed: () {sample.submit()},
+ onPressed: () {sample.submit(_controller.text)},
child: const Text('登録'),
),
class SamplePageNotifier extends StateNotifier<SamplePageState> {
final Ref ref;
SamplePageNotifier(this.ref) : super(SamplePageState());
// 登録する
- void submit() {
- // 登録処理
- }
+ void submit(String text) {
+ // 登録処理
+ }
}
こうなってくるともはやstateを保持する意味がほとんどなくなるケースもあるかと思います
まだ項目が一つなので問題なさそうに見えますが、フォームが増えてくると、その分全てを送らないといけないのでかなり面倒なことになります
なので、stateとTextEditingControllerのtextを同期させたくないケースくらいしか使わないかなと思います(そんなケースはあまり思い浮かばないので、つまりほとんど使わないと思います)
②freezedで管理する
freezedクラスにTextEditingControllerを埋め込み、stateとして保持する方法です
@freezed
class SamplePageState with _$SamplePageState {
const factory SamplePageState({
- @Default("") String text,
+ TextEditingController text,
}) = _SamplePageState;
}
class SampleView extends ConsumerWidget {
const SampleView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final sample = ref.watch(sampleNotifierProvider.notifier);
return Scaffold(
appBar: AppBar(
title: const Text("サンプルアプリ"),
),
body: Column(
children: [
TextField(
+ controller: sample.state.text,
maxLines: 3,
minLines: 1,
以下略
この場合、TextEditingControllerをstateとして管理することに煩わしさを感じるかどうかです
また、stateをcopyWithで置き換える際に、毎回新しいTextEditingControllerに置き換えることになるのですが、あまり必要な場面はなく、無駄になりやすい処理かなと思います(かといってあまり気にすることもないのですが)
③Notifierで管理する
NotifierにTextEditingControllerを定義する方法です
個人的にはこれが一番丸いのではないかと思っています
class SamplePageNotifier extends StateNotifier<SamplePageState> {
final Ref ref;
SamplePageNotifier(this.ref) : super(SamplePageState());
+ TextEditingController _controller = TextEditingController();
// 登録する
void submit() {
// 登録処理
}
}
class SampleView extends ConsumerWidget {
const SampleView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final sample = ref.watch(sampleNotifierProvider.notifier);
return Scaffold(
appBar: AppBar(
title: const Text("サンプルアプリ"),
),
body: Column(
children: [
TextField(
+ controller: sample._controller,
maxLines: 3,
minLines: 1,
以下略
Notifier内に定義しておくことで、Notifier内の登録処理実行時にTextEditingControllerのtextを参照することができるので、処理もやりやすく、stateも同期しやすいと思います
まとめ
正直②と③に関しては好みのところも大きいので、一概にどちらが良いとは言えないですが、TextEditingControllerの管理方法を決める際の手助けにならば幸いです
私自身、Flutter勉強中のみですので、記事に間違いや指摘事項等あれば教えていただけると幸いです
最後まで見ていただきありがとうございました
8日目のTechCommit AdventCalendar2022もお楽しみに