🪶

初学者すぎてRiverpodでのDIが分からなかったのでメモ

2023/04/11に公開

概要

Flutter超初学者です。
RiverpodでDIする第一歩を個人的にメモ。

やってみた

pubspec.yamlにflutter_riverpodを追記

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^2.3.4

動物の名前の文字列の配列を返すリポジトリのインターフェースを定義し、
そのインターフェースのプロバイダを定義(後にこのプロバイダに実装を注入する)

animal_i_repository.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

// インターフェースの型のProviderを定義
// 後に実際の実装を注入する
// 注入がされない場合は[UnimplementedError]をthrow
final animalRepositoryProvider = Provider<IAnimalRepository>(
  (_) => throw UnimplementedError(),
);

// インターフェース
abstract class IAnimalRepository {
  List<String> findAll();
}

インターフェースを実装(簡易的に配列をそのまま返すだけ)

animal_repository.dart
import 'package:riverpod_di/animal_i_repository.dart';

class AnimalRepository implements IAnimalRepository {
  AnimalRepository();

  
  List<String> findAll() {
    return ['ゾウ', 'ライオン', 'キリン'];
  }
}

ProviderScopeのoverridesで対象のプロバイダに実装を注入
(画面は動物名をListViewで並べるだけ)

main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_di/animal_i_repository.dart';
import 'package:riverpod_di/animal_repository.dart';

void main() {
  // ProviderScopeのoverridesで注入する
  runApp(ProviderScope(overrides: [
    animalRepositoryProvider.overrideWithValue(AnimalRepository()),
  ], child: MyApp()));
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}

final animalsProvider = Provider((ref) {
  final animalRepository = ref.watch(animalRepositoryProvider);
  return animalRepository.findAll();
});

class HomePage extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final animals = ref.watch(animalsProvider);
    return Scaffold(
        appBar: AppBar(
          title: const Text('DI demo'),
        ),
        body: ListView.builder(
            itemCount: animals.length,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text(animals[index]),
              );
            }));
  }
}

以下のような画面になりました!
これで第一歩は踏めているのか...??

WidgetTestでmockのリポジトリを注入してみます。
インターフェースを実装したmockリポジトリを作成し、pumpWidgetでの生成時に同じように注入します。

widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'package:riverpod_di/main.dart';
import 'package:riverpod_di/animal_i_repository.dart';

void main() {
  testWidgets('dummyデータが表示されるかのテスト', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(ProviderScope(overrides: [
      animalRepositoryProvider.overrideWithValue(MockAnimalRepository()),
    ], child: MyApp()));

    expect(find.text('dummyゾウ'), findsOneWidget);
    expect(find.text('dummyライオン'), findsOneWidget);
    expect(find.text('dummyキリン'), findsOneWidget);
  });
}

// インターフェースを実装したモックのリポジトリ
class MockAnimalRepository implements IAnimalRepository {
  MockAnimalRepository();

  
  List<String> findAll() {
    return ['dummyゾウ', 'dummyライオン', 'dummyキリン'];
  }
}

テストも通りました。

✓ dummyデータが表示されるかのテスト
Exited

きっと色々間違っていたりするんだろうけど、一歩は踏めていると思うのでヨシ!!!

Discussion