Closed20
2023.05~ 日々の学び
Flutter Performance
ブリーチなreact
Flutterエンジニアに求められる能力
Cyber Agent
DeNA
PID制御
FlutterアプリのAndroidでFirebase Performanceに疎通できない
Flutter導入企業
Google Play の対象 API レベルの要件を満たす
RiverpodのAsyncValue
- Sealed Class
- Sub Classを制限する
- Enumのようなイメージ
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,
}) {
Unreal Engineの環境構築
brew install --cask epic-games
アプリを起動
サインイン
AndroidでFirebase Google認証
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null, null)
keytool -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore
- debug用のフィンガープリントは追加済み
- google-services.jsonもandroid/appに配置済み
なのにエラーが出る
パッケージ名の変更
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'
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を最新のものにする必要がある
最新バージョンはここで確認する
flutterfire_cliのバグらしい
Riverpod Generator + AsyncNotifier
初期化できない
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)])>
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にクローズされました