Closed20

2023.05~ 日々の学び

ひろとひろと

Navigator.of(context).pushNamed()を押したときに走る処理

SampleView.dart

-ボタンをクリック

  • onPressedのNavigator.of(context).pushNamed

flutter/lib/src/widgets/navigator.dart

Navigatorクラスのofメソッドを実行

  static NavigatorState of(
    BuildContext context, {
    bool rootNavigator = false,
  }) {
ひろとひろと

AndroidでFirebase Google認証

https://firebase.google.com/docs/auth/flutter/federated-auth?hl=ja
https://developers.google.com/android/guides/client-auth?hl=ja

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null, null)

https://riscait.medium.com/apiexception-10-error-in-sign-in-with-google-using-firebase-auth-in-flutter-1be6a44a2086

keytool -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore
  • debug用のフィンガープリントは追加済み
  • google-services.jsonもandroid/appに配置済み

なのにエラーが出る

パッケージ名の変更
https://qiita.com/osamu1203/items/6adfab47e562f5e7d03b

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:processDebugGoogleServices'.
> No matching client found for package name 'com.example.federer'

https://tns-blog.com/261
android/app/build.gradleのapplicationIdがgoogle-services.jsonのpackage_nameと一致する必要がある。

BUILD FAILED in 21s
┌─ Flutter Fix ──────────────────────────────────────────────────────────────────────────────────┐
│ [!] Your project requires a newer version of the Kotlin Gradle plugin.                         │
│ Find the latest version on https://kotlinlang.org/docs/gradle.html#plugin-and-versions, then   │
│ update /Users/suzukitarou/Workspace/private/flutter_app/Federer/frontend/android/build.gradle: │
│ ext.kotlin_version = '<latest-version>'                                                        │
└────────────────────────────────────────────────────────────────────────────────────────────────┘

android/build.gradleのext.kotlin_versionを最新のものにする必要がある
最新バージョンはここで確認する
https://github.com/JetBrains/kotlin/releases

https://zenn.dev/taiki_asakawa/articles/fd59b91371f546
android/app/build.gradleとandroid/build.gradleに手動で設定を追加する必要がある
flutterfire_cliのバグらしい

ひろとひろと

初期化できない

FriendStateControllerのbuild時点でfetchFriendListを実行しておきたい。
けどできない、、!
buildの中でfetchFriendListをcallするとloadingのまま終わらなくなる

// Package imports:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'friend_state.freezed.dart';
part 'friend_state.g.dart';


class FriendState with _$FriendState {
  const factory FriendState({
    (AsyncValue<List<Friend>>.data([]))
        AsyncValue<List<Friend>> friendList,
    AppError? error,
  }) = _FriendState;
}

(keepAlive: true)
class FriendStateController extends _$FriendStateController {
  late FriendRepository _friendRepository;

  
  FriendState build() {
    Log.d('FriendStateController build');
    _friendRepository = ref.watch(friendRepositoryProvider);
    return const FriendState();
  }

  void clearError() {
    state = state.copyWith(error: null);
  }

  Future<void> fetchFriendList() async {
    Log.d('fetchFriendList');
    state = state.copyWith(friendList: const AsyncValue.loading());
    final friendList = await _friendRepository.fetchFriendList();
    state = state.copyWith(friendList: AsyncValue.data(friendList));
  }
}
ひろとひろと

無理やり感もあるけど初期化できた

view_model.dart

part 'friend_state.freezed.dart';
part 'friend_state.g.dart';


class FriendState with _$FriendState {
  const factory FriendState({
    (AsyncValue<List<Friend>>.data([]))
        AsyncValue<List<Friend>> friendList,
    (false) bool isInitialized,
    AppError? error,
  }) = _FriendState;
}

(keepAlive: true)
class FriendStateController extends _$FriendStateController {
  
  FriendState build() {
    return const FriendState();
  }

  void initialize() {
    if (!state.isInitialized) {
      fetchFriendList();
      state = state.copyWith(isInitialized: true);
    }
  }

  void clearError() {
    state = state.copyWith(error: null);
  }

  Future<void> fetchFriendList() async {
    state = state.copyWith(friendList: const AsyncValue.loading());
    final friendList =
        await ref.read(friendRepositoryProvider).fetchFriendList();
    state = state.copyWith(friendList: AsyncValue.data(friendList));
  }
}

view.dart

// Flutter imports:
import 'package:flutter/material.dart';

// Package imports:
import 'package:flutter_riverpod/flutter_riverpod.dart';

class FriendPage extends ConsumerWidget {
  const FriendPage({
    super.key,
  });

  
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(friendStateControllerProvider);
    final controller = ref.watch(friendStateControllerProvider.notifier);
    
    // 初期化
    Future.microtask(controller.initialize);

    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          OutlinedButton(
            onPressed: controller.fetchFriendList,
            child: const Text('fetch'),
          ),
          state.friendList.when(
            loading: () {
              return const Center(
                child: CircularProgressIndicator(),
              );
            },
            data: (friendList) => SizedBox(
              height: 500,
              child: ListView.builder(
                itemCount: friendList.length,
                itemBuilder: (context, index) {
                  final friend = friendList[index];
                  return ListTile(
                    title: Text(friend.name),
                  );
                },
              ),
            ),
            error: (error, stackTrace) => Center(
              child: Text(error.toString()),
            ),
          ),
        ],
      ),
    );
  }
}

Repository

class FriendRepository with ApiErrorConverterMixin {
  Future<List<Friend>> fetchFriendList() async {
    await Future<void>.delayed(const Duration(seconds: 1));
    return [const Friend(name: '友達1'), const Friend(name: '友達2')];
  }
}

final friendRepositoryProvider = Provider<FriendRepository>((ref) {
  return FriendRepository();
});
ひろとひろと

Notifierのテスト

完全に同じに見えるけど何が違うのだろう

      test('[正常系]初期化した時にリストを取得している', () async {
        mockSetup(
          mockFriendRepository: mockFriendRepository,
        );
        final controller =
            container.read(friendStateControllerProvider.notifier);

        expect(controller.state.isInitialized, false);
        controller.initialize();
        await Future<void>.delayed(const Duration(milliseconds: 100));
        expect(
          controller.state.isInitialized,
          true,
        );
        expect(
          controller.state.friendList,
          AsyncData<List<Friend>>([createFriend()]),
        );
      });
    });
Expected: AsyncData<List<Friend>>:<AsyncData<List<Friend>>(value: [Friend(userFriendId: 0, userId: 0, friendUserId: 0, name: , profileImageUrl: , level: 1)])>

Actual: AsyncData<List<Friend>>:<AsyncData<List<Friend>>(value: [Friend(userFriendId: 0, userId: 0, friendUserId: 0, name: , profileImageUrl: , level: 1)])>

ひろとひろと

https://qiita.com/Seo-4d696b75/items/eee020162d0537fdbc36
StateNotifierと違って、AsyncNotifier, NotifierにはdebugState, streamが存在しない

listenerで状態のテストをする.

void mockSetup({
  required MockFriendRepository mockFriendRepository,
  required List<Friend> friendList,
  bool exception = false,
}) {
  setMockFriendRepository(
    mockFriendRepository: mockFriendRepository,
    friendList: friendList,
    exception: exception,
  );
}

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  late ProviderContainer container;
  late MockFriendRepository mockFriendRepository;

  setUp(() {
    mockFriendRepository = MockFriendRepository();
    container = ProviderContainer(
      overrides: [
        friendRepositoryProvider.overrideWithValue(mockFriendRepository),
      ],
    );
  });

  group('FriendStateController', () {
    group('FriendStateController.fetchFriendList', () {
      test(
        '[正常系] friendListの取得に成功する',
        () async {
          final friendList = [createFriend()];
          mockSetup(
            mockFriendRepository: mockFriendRepository,
            friendList: friendList,
          );

          // listenerの設定
          final listener = MockListener();
          container.listen(
            friendStateControllerProvider,
            listener,
            fireImmediately: true,
          );

          // fetchFriendListをcall
          final controller =
              container.read(friendStateControllerProvider.notifier);
          await controller.fetchFriendList();

          verifyInOrder([
            // friendListの初期化
            listener.call(
              null,
              const FriendState(
                friendList: AsyncValue.data([]),
                isInitialized: false,
              ),
            ),
            // friendListのローディング中
            listener.call(
              const FriendState(
                friendList: AsyncValue.data([]),
              ),
              const FriendState(
                friendList: AsyncValue.loading(),
              ),
            ),
            // friendListの取得成功
            listener.call(
              const FriendState(
                friendList: AsyncValue.loading(),
              ),
              FriendState(
                friendList: AsyncValue.data(friendList),
              ),
            ),
          ]);
        },
      );

      test('[異常系] friendListの取得に失敗する', () async {
        mockSetup(
          mockFriendRepository: mockFriendRepository,
          friendList: <Friend>[],
          exception: true,
        );

        // listenerの設定
        final listener = MockListener();
        container.listen(
          friendStateControllerProvider,
          listener,
          fireImmediately: true,
        );

        // fetchFriendListをcall
        final controller =
            container.read(friendStateControllerProvider.notifier);
        await controller.fetchFriendList();

        verifyInOrder([
          // friendListの初期化
          listener.call(
            null,
            const FriendState(
              friendList: AsyncValue.data([]),
              isInitialized: false,
            ),
          ),
          // friendListのローディング中
          listener.call(
            const FriendState(
              friendList: AsyncValue.data([]),
            ),
            const FriendState(
              friendList: AsyncValue.loading(),
            ),
          ),
          // friendListの取得成功
          listener.call(
            const FriendState(
              friendList: AsyncValue.loading(),
            ),
            const FriendState(
              friendList: AsyncValue.error(
                true,
                StackTrace.empty,
              ),
            ),
          ),
        ]);
      });
    });
  });
}

このスクラップは2023/06/19にクローズされました