flutter_hooks + riverpodでViewModel風
flutterではUI(Widget)が、状態を持つことができます(StatefulWidget)
そして状態を変更すると自動でUIに反映されます
Androidで言えば、データバインディングをデフォルトサポートしている感じでしょうか
↓Flutterのデモアプリで考えてみます
普通にFlutterで状態管理した場合
以下はFlutterプロジェクトを作成して自動で生成されるサンプルコードです
setState関数を使ってStatefulWidgetが持つ状態を更新すると、自動でbuild関数が実行されてUIも更新されます
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() { // <<<<< ここで状態を更新する
_counter++;
});
}
@override
Widget build(BuildContext context) {
// 状態を更新するとbuildが再実行されてUIが更新される
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
何とかしたいこと
私が作っているアプリはそれほど規模が大きくありませんが、それでもふたつ問題が出てきました
とにかくアプリが重い
変化しうるWidgetを全てStatefulWidgetで書いていたのですが、アプリの動きがカクつきがちでした
というのもFlutterでは入れ子になっているWidgetが更新されると、親のWidgetからまとめて更新されてしまうようです
例えばReactだと差分だけ更新してくれたりするんですが...
画面間での状態共有が難しい
複数の画面で同じ状態を共有しようとするとちょっと難しいです
例えば根っこのWidgetで状態をまとめて管理することが挙げられますが、親子Widget間でコールバックを受け渡すことになりそうです
flutter_hooks + riverpodで解決する
そこでflutter_hooksとriverpodで解決を図りました
正直なところ仕組みを全然把握できていないのでアレですが、非常にシンプルには書けたと思います
バージョンとか
- Flutter 2.0.6
- MacOS Big Sur 11.2.3
- AndroidStudio 4.1.2
準備
まずはパッケージを追加します。それぞれのバージョンは2021/05/03の最新バージョンです
dependencies:
flutter:
sdk: flutter
flutter_hooks: ^0.16.0. # <<<<< 追加
hooks_riverpod: ^0.13.1. # <<<<< 追加
実装
ViewModel
今回は整数型のcounterしか状態がないので本当はもっとシンプルに書けると思いますが、拡張性を考えてMainViewModelStateクラスにまとめてみました
import 'package:flutter/cupertino.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
final mainViewModelNotifierProvider = StateNotifierProvider( (_) => MainViewModel());
class MainViewModelState {
int counter;
MainViewModelState({@required this.counter});
}
final initialState = MainViewModelState(counter: 0);
class MainViewModel extends StateNotifier<MainViewModelState> {
MainViewModel() : super(initialState);
void incrementCounter() {
state = MainViewModelState(counter: state.counter + 1);
}
}
View (ViewModelを使う方)
書き方はいくつかあると思いますが、私はHookBuilderを使うのがシンプルで好みでした
ViewModelはグローバルに参照できるので、複数の画面でViewModelの更新を受信することができますし、もちろんViewModelの関数を複数画面から実行することもできます
加えて、StatefulWidgetを使っていたところをStatelessWidgetにすることができるので、状態更新に伴うWidgetの更新範囲を絞ることができます
おそらく処理速度的にはいくらかマシなはず
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_mvvm_sample/main_view_model.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
runApp(
ProviderScope( // <<<<< 追加
child: MyApp()
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget { // <<<<< Statelessに変更
const MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
counterText(),
],
),
),
floatingActionButton: incrementButton(),
);
}
}
Widget incrementButton() {
return HookBuilder(builder: (BuildContext context) {
final controller = useProvider(mainViewModelNotifierProvider);
return FloatingActionButton(
onPressed: () => controller.incrementCounter(),
tooltip: 'Increment',
child: Icon(Icons.add),
);
});
}
Widget counterText() {
return HookBuilder(builder: (BuildContext context) {
// ViewModelの状態が更新されたら再実行される
final state = useProvider(mainViewModelNotifierProvider.state);
return Text(
'${state.counter}',
style: Theme.of(context).textTheme.headline4,
);
});
}
まとめ
ということで、flutter_hooks + riverpodでシンプルにViewModelを書いてみました
これで画面間の状態共有がかなりシンプルに書けています
ただし繰り返しになりますが、flutter_hooksやriverpodの仕組み自体はほとんど把握できていなかったりします。先人たちの見様見真似で何とか実装できた感じです
より良い実装の方法や、仕組みがわかる資料などがありましたら教えていただけると助かります!
Author And Source
この問題について(flutter_hooks + riverpodでViewModel風), 我々は、より多くの情報をここで見つけました https://qiita.com/nashitake/items/d34e0e315d6d777462be著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .