【Flutter】実務で求められるコーディング - その2
このシリーズでは僕が実際にFlutterエンジニアとしてインターンやアルバイトをして学んだ、実務で求められるコーディングについて紹介していきます。
コードチェック時にもらった、ベテランエンジニアさんからのアドバイスや、チーム開発をする上で意識すべきことなど、独学では得ることができなかった体験をもとにお伝えします。
僕がアルバイトをはじめた最初の頃のスキル感 ↓
大体こんな感じだったので、実際にプロジェクトにジョインしたときはついていくのに必死でした。
今Flutterを独学で学ばれている方は、インターンやアルバイトに応募する際にこの記事が役に立てればと思います。
providerとriverpodをマスターしよう
実務では変数の状態を管理する際にProvider、Riverpodのどちらかのパッケージを使うことが非常に多く、これを使えるようにしておくと、独学とのギャップをかなり埋めることができると思います。
providerの使用例↓
riverpodの公式ドキュメント↓
公式が出している上記の使用例を参考に、簡単に二つのパッケージの使い方を紹介します。
providerの使い方
前提として、カタログのリストを表示し、カタログをカートに入れるという一連の動作をproviderを使ってやっていきます。
Model作成
まず、変数の状態を管理するCartModelクラスを作ります。
class CartModel extends ChangeNotifier {
/// プライベートなカート内部の状態(他のクラスからは参照できない)
final List _items = [];
/// カート内のアイテムの変更不可能なリスト
UnmodifiableListView get items => UnmodifiableListView(_items);
すべてのアイテムの現在の合計価格(すべてのアイテムの価格が42ドルであると想定)。
int get totalPrice => _items.length * 42;
/// 引数のitemをカートに追加。 [add]と[removeAll]は、外からカートの状態を変更する唯一の方法です。
void add(item) {
_items.add(item);
// この呼び出しは、このCartModelをlistenしているウィジェットに再構築するように指示します。
notifyListeners();
}
/// カートから全てのアイテムを削除します。
void removeAll() {
_items.clear();
// この呼び出しは、このCartModelをlistenしているウィジェットに再構築するように指示します。
notifyListeners();
}
}
コメントにある通り、ここではカート内部の状態を管理するCartModelを作成し、[add],[removeAll]を通して_itemsの状態を変えることができるようにしています。
また、getterを使って定義されているitemsは外から参照するときのみ使われます。
ChangeNotifierProvider, MultiProvider
そして大元のwidgetをChangeNotifierProviderまたは、MultiProviderでラップします。
一つのプロジェクトにつき、複数のクラスで状態を管理する場合にはMultiProvierを使いましょう。
ChangeNotifierProvider
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: const MyApp(),
),
);
}
MultiProvider
main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CartModel()),
Provider(create: (context) => SomeOtherClass()),
],
child: const MyApp(),
),
);
}
ウィジェットに通知
最後にさっき作った関数を呼び出し、notifyListeners()によってウィジェットに通知されることで、画面に反映されます。
onTap: (item) {
final model = Provider.of<Model>(context, listen: false);
model.add(item);
}
通知を受け取ることができるウィジェットはConsumerとSelectorの二つが用意されています。ここでは、Consumerウィジェットを使います。
Consumer<CartModel>(
builder: (context, cart, child) {
return Text('Total price: ${cart.totalPrice}');
},
);
Consumerウィジェットは、listenしているModelクラスでnotifierListeners()が呼ばれた際に必ず通知を受け取りますが、
Selectorウィジェッでは、Modelクラスのどの変数をlistenするかを一つ指定することができます。
よって、「一つの変数だけlistenしたい」というときはSelectorを使いましょう。
riverpodの使い方
今度は、先程と同じ動作をriverpodでやってみようと思います。
手順はほとんど同じです。
Provider
まず、大元のウィジェットをProviderScopeでラップします
void main() {
runApp(const ProviderScope(child: MyApp()));
}
Model作成
final cartModel = Provider((ref) => CartModel(ref.read));
/// カート内部の状態
final itemsState = StateProvider<List>((ref) => []);
///すべてのアイテムの現在の合計価格(すべてのアイテムの価格が42ドルであると想定)。
final totalPriceState = StateProvider<int>(
(ref) => ref.read(itemsState.notifier).state.length * 42);
class CartModel {
CartModel(this._read);
final Reader _read;
/// 引数のitemをカートに追加。
void add(item) {
_read(itemsState.notifier).update((state) => [...state, item]);
}
/// カートから全てのアイテムを削除します。
void removeAll() {
_read(itemsState.notifier).update((state) => []);
}
}
またCartModelを作りましたが、itemsとtotalPriceはクラスの外でStateProviderで定義します。これにより、providerのメソッドread(),watch()を利用すれば、状態が変更された場合、自動的に通知されるようになります。
そして、CartModelクラスをProviderで定義しておくことで、readメソッドを利用すれば、どこからでもCartModelクラスのメソッドを呼び出すことができるようになります。また、CartModelクラスにreadメソッドを渡すことができ、クラス内でもProviderで定義された値を呼び出すことができます。
ウィジェットに通知
最後にCartModelのaddメソッドを呼び出し、StateProviderの状態が変更された時に通知を受け取ことができるように、watchメソッドでwatchしたい値を定義します。
onTap: (item) {
ref.read(cartModel).add(item);
}
class CartScreen extends HookConsumerWidget {
const Home({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final totalPrice = ref.watch(totalPriceState.notifier).state;
return Scaffold(
状態管理でやりがちなミス
状態管理で、僕が実際にやりがちだった間違いを紹介します。
かなり初歩的なことですが、知らないとやってしまうことなので確認してみてください。
Listの状態管理
List型の状態の変更は参照を変更しないと、通知されませんが、僕はよく同じ参照を渡して、通知されないみたいなことがよくありました。
具体的にどういうことか、riverpodを使った際のコードを見てみましょう。
final listModel = Provider((ref) => ListModel(ref.read));
final itemsState = StateProvider<List>((ref) => []);
class ListModel {
CartModel(this._read);
final Reader _read;
void add(item) {
//参照が変わっていない
_read(itemsState.notifier).state.add(item);
}
}
add()を使った場合、参照が変わっていないため、状態の変更が通知されません。その代わり、このような書き方をするとうまくいきました。
final listModel = Provider((ref) => ListModel(ref.read));
final itemsState = StateProvider<List>((ref) => []);
class ListModel {
CartModel(this._read);
final Reader _read;
void add(item) {
_read(itemsState.notifier).update((state) => [...state, item]);
}
}