🌸

Riverpodでweb apiとsqliteの結果を結合してさらにフィルタをかける

2024/03/16に公開

個人情報は、デバイスのストレージに保存して、それ以外のマスタ的な情報はweb apiから取得し、さらにユーザが表示させるデータをフィルタリングできるサンプルを作ってみました。

https://youtube.com/shorts/MFvsfs8spEo?feature=share

https://github.com/na8esin/riverpod_sqflite_sample/commit/0759ef9c1139635ce73baf41ae229c51e7d5de34

タイトル部分で出ただけでも、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