🌸
Riverpodでweb apiとsqliteの結果を結合してさらにフィルタをかける
個人情報は、デバイスのストレージに保存して、それ以外のマスタ的な情報はweb apiから取得し、さらにユーザが表示させるデータをフィルタリングできるサンプルを作ってみました。
タイトル部分で出ただけでも、sqlite用と、api用と、フィルタの状態管理用とで3つのProviderが必要になります。
これらのProviderを一つのWidget内に登場させるとコードが複雑になってしまうので、Provider同士を結合させる必要があります。
// sqliteからデータを取得
Future<List<UserProduct>?> allUserProducts(AllUserProductsRef ref) async {
final repository = await ref.watch(userProductRepositoryProvider.future);
return repository.findAll();
}
// apiからデータを取得。
// サンプルなので、名前に反して、10件で制限してます
Future<List<Product>?> allProducts(AllProductsRef ref) async {
final dio = Dio();
final response = await dio.get('https://dummyjson.com/products?limit=10');
// 簡単のため、地道にキャストしてく
final data = response.data as Map<String, dynamic>;
final products = data['products'] as List<dynamic>;
return products.map((e) => Product.fromJson(e)).toList();
}
// sqliteのデータとapiのデータを結合する
class UserDevicesNotifier extends _$UserDevicesNotifier {
Future<List<UserDevice>?> build() async {
final userProducts = await ref.watch(allUserProductsProvider.future);
// このproviderが破棄されるタイミングでデータをDBに同期する
// 動作未確認
ref.onDispose(() async {
await syncData();
});
final userProductIds = userProducts?.map((e) => e.id);
if (userProductIds == null) return null;
// api側にidの配列を受け取るようなエンドポイントがあればよかったけど、
// なかったのでその下の処理が複雑になってます
final products = await ref.watch(allProductsProvider.future);
return userProducts?.map((e) {
final filterd = products?.firstWhere((p) => p.id == e.id);
return UserDevice(id: e.id, name: filterd?.title ?? "", inUse: e.inUse);
}).toList();
}
上記から、riverpod_generatorで生成されるuserDevicesNotifierProviderとフィルタ用のProviderを組み合わせます。
// フィルターの状態。
// `StateProvider is to be avoided` と↓に書かれてるので公式のTODOサンプルを書き換えたもの。
// https://riverpod.dev/ja/docs/migration/from_state_notifier#from-stateprovider
class ListFilterNotifier extends _$ListFilterNotifier {
DeviceState build() => DeviceState.all;
set state(DeviceState newState) => super.state = newState;
DeviceState update(DeviceState Function(DeviceState state) cb) =>
state = cb(state);
}
// ↑のProviderとさっき作ったuserDevicesNotifierProviderを組み合わせたもの
Future<List<UserDevice>?> filteredDevices(FilteredDevicesRef ref) async {
final filter = ref.watch(listFilterNotifierProvider);
final devices = await ref.watch(userDevicesNotifierProvider.future);
switch (filter) {
case DeviceState.busy:
return devices?.where((e) => e.inUseToBool()).toList();
case DeviceState.unoccupied:
return devices?.where((e) => !e.inUseToBool()).toList();
case DeviceState.all:
return devices;
}
}
ここから先のコードは公式のTODOサンプルとほとんど同じになります。
Discussion