Slide 1

Slide 1 text

࣮ફతύοέʔδઓུ FlutterKaigi 2024

Slide 2

Slide 2 text

About Me ҏ౻ ګฏ גࣜձࣾαΠόʔΤʔδΣϯτ Github: KyoheiG3 X: KyoheiG3

Slide 3

Slide 3 text

ΞδΣϯμ •֓ཁ •ύοέʔδ෼ׂͷ࣮૷ྫ •ύοέʔδͷ໾ׂ •ςετʹ͍ͭͯ

Slide 4

Slide 4 text

ΞδΣϯμ •֓ཁ •ύοέʔδ෼ׂͷ࣮૷ྫ •ύοέʔδͷ໾ׂ •ςετʹ͍ͭͯ

Slide 5

Slide 5 text

ύοέʔδ෼ׂͷύλʔϯྫ • ΞϓϦέʔγϣϯͱ cli ͳͲͷπʔϧ • ௨৴෦෼ͳͲͷ෦඼୯Ґ • ෳ਺ͷΞϓϦέʔγϣϯͱڞ௨ػೳ • ػೳ୯Ґ • ϨΠϠʔ • ෼͚ͳ͍

Slide 6

Slide 6 text

ΞϓϦΛී௨ʹ։ൃ͢Δͱ ͜ͷܗʹͳΔ App ෼͚ͳ͍

Slide 7

Slide 7 text

෦඼୯ҐͰ෼͚Δ ੾Γग़͞Εͨ෦඼ͷ୯ମς ετ͕΍Γ΍͘͢ͳΔ Network Location DB App

Slide 8

Slide 8 text

ڞ௨ػೳͷ੾Γग़͠ Core ػೳΛڞ༗͢ΔΞϓϦ ͕૿͑Δͱෆཁͳػೳ͕૿ ͑Δ Core App

Slide 9

Slide 9 text

ػೳ୯ҐͰ෼͚Δ ଞͷػೳ΁ͷӨڹΛ͋·Γ ߟ͑ͳ͍͍ͯ͘൓໘ɺػೳ ಉ࢜ͷ࿈ܞ͕೉͍͠ App Core Noti fi cation Purchase Login

Slide 10

Slide 10 text

ϨΠϠʔͰ෼͚Δ ֎ଆͷϨΠϠʔ͸಺ଆͷϨ ΠϠʔʹґଘ͍ͯ͠Δ Presentation App Application Infra Domain

Slide 11

Slide 11 text

ΫϦʔϯΞʔΩςΫνϟ ը૾ग़లɿThe Clean Code Blog by Robert C. Martin (Uncle Bob) | The Clean Architecture

Slide 12

Slide 12 text

ΫϦʔϯΞʔΩςΫνϟ ґଘੑٯస App UI Driver Presenter Gateway Entity UseCase

Slide 13

Slide 13 text

• Ͳ͜ͰԿΛॲཧ͢Δ͔͕໌֬ʹͳ࣮ͬͯ૷ʹڧ੍ྗ͕ੜ·ΕΔ • DI ͕ඞਢͳͷͰࣗͣͱϢχοτςετͷ࢓૊Έ͕੔͏ • ςετͷࡍʹෆཁͳύοέʔδΛഉআͰ͖Δ • ύοέʔδ΁ͷॲཧΛฒྻʹߦ͑ΔΑ͏ʹͳΔ ෼ׂ࣌ͷϝϦοτ

Slide 14

Slide 14 text

෼ׂ࣌ͷσϝϦοτ • ׳ΕΔ·Ͱࠞཚ͕ͪ͠ • ΠϯλʔϑΣʔεͳͲͷهड़͕૿͑Δ • ίʔυδϟϯϓ͕໘౗ʹͳΔ

Slide 15

Slide 15 text

ΞδΣϯμ •֓ཁ •ύοέʔδ෼ׂͷ࣮૷ྫ •ύοέʔδͷ໾ׂ •ςετʹ͍ͭͯ

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

riverpod • ΞϓϦશମͷঢ়ଶ؅ཧ • ґଘͷ஫ೖ͕༰қʢDIʣ • ϥϯλΠϜͰ͸ͳͯ͘ίϯύΠϧλΠϜͰܕͷΤϥʔ͕෼͔Δʢܕ҆શʣ

Slide 18

Slide 18 text

Ϣʔβ໊Λදࣔ͢Δ͚ͩͷ ΞϓϦΛ࣮૷ͯ͠ΈΔ • Repository ܦ༝Ͱσʔλ औಘ • UseCase ͰσʔλΛՃ޻ • UI ʹදࣔ

Slide 19

Slide 19 text

App ύοέʔδͳͲಛʹߟ͑ͣʹ࡞Δ

Slide 20

Slide 20 text

app/lib/src/repository/user.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'user.g.dart'; @riverpod UserRepository userRepository(UserRepositoryRef ref) => UserRepository(ref); class UserRepository { const UserRepository(this.ref); final UserRepositoryRef ref; Future getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User(response.id, response.name); } }

Slide 21

Slide 21 text

app/lib/src/state/user.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../repository/user.dart'; part 'user.g.dart'; @riverpod class UserStateNotifier extends _$UserStateNotifier { @override Future build() async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } }

Slide 22

Slide 22 text

app/lib/src/widget/user_name.dart import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../state/user.dart'; class UserName extends ConsumerWidget { const UserName({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final userState = ref.watch(userStateNotifierProvider); return userState.when( data: (state) { return Text('User name: ${state.user?.name}'); }, error: (error, _) { return Text('Error: $error'); }, loading: () { return const CircularProgressIndicator(); }, ); } }

Slide 23

Slide 23 text

ΞϓϦ࣮ߦ UserRepository ͕ฦͨ͠ Ϣʔβ໊͕දࣔ͞ΕΔ

Slide 24

Slide 24 text

App App ϨΠϠʔ͝ͱʹύοέʔδΛ෼͚Δ

Slide 25

Slide 25 text

ϨΠϠʔͷྫ • ΠϯϑϥϨΠϠʔ • DB ΍ API ʹΞΫηεͨ͠Γ͢Δ • Repository ͳͲ • ΞϓϦέʔγϣϯϨΠϠʔ • ϏδωεϩδοΫ͕ू໿͞ΕΔ • UseCase ͳͲ

Slide 26

Slide 26 text

ΫϦʔϯΞʔΩςΫνϟ ͜ͷ͋ͨΓΛࢀߟʹ ύοέʔδΛ໋໊

Slide 27

Slide 27 text

ύοέʔδͷྫ • ΠϯϑϥϨΠϠʔ • Gateway ύοέʔδΛ࡞੒ • UserRepository ΛҠಈ • ΞϓϦέʔγϣϯϨΠϠʔ • UseCase ύοέʔδΛ࡞੒ • UserStateNoti fi er ΛҠಈ

Slide 28

Slide 28 text

Gateway ύοέʔδͷ࡞੒ $ flutter create -t package gateway $ mkdir -p gateway/lib/src/repository $ mv app/lib/src/repository/user.dart gateway/lib/src/repository/user.dart

Slide 29

Slide 29 text

Gateway ύοέʔδͷ࡞੒ name: gateway description: Gateway for infrastructure Layer environment: sdk: '>=3.3.0 <4.0.0' dependencies: riverpod_annotation: dev_dependencies: build_runner: riverpod_generator:

Slide 30

Slide 30 text

Gateway ύοέʔδͷ࡞੒ export 'src/repository/user.dart';

Slide 31

Slide 31 text

UseCase ύοέʔδͷ࡞੒ $ flutter create -t package usecase $ mkdir -p usecase/lib/src/state $ mv app/lib/src/state/user.dart usecase/lib/src/state/user.dart

Slide 32

Slide 32 text

UseCase ύοέʔδͷ࡞੒ name: usecase description: UseCase for domain Layer environment: sdk: '>=3.3.0 <4.0.0' dependencies: riverpod_annotation: gateway: path: ../gateway dev_dependencies: build_runner: riverpod_generator: HBUFXBZύοέʔδΛ ґଘͱͯ͠௥Ճ͢Δ

Slide 33

Slide 33 text

UseCase ύοέʔδͷ࡞੒ import 'package:riverpod_annotation/riverpod_annotation.dart'; // import '../repository/user.dart'; import 'package:gateway/gateway.dart'; part 'user.g.dart'; @riverpod class UserStateNotifier extends _$UserStateNotifier { @override FutureOr build() async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } } HBUFXBZύοέʔδΛ JNQPSU͢Δ HBUFXBZύοέʔδͷ 3FQPTJUPSZΛࢀরͰ͖Δ

Slide 34

Slide 34 text

UseCase ύοέʔδͷ࡞੒ export 'src/state/user.dart';

Slide 35

Slide 35 text

ΞϓϦຊମ name: app description: Application environment: sdk: '>=3.3.0 <4.0.0' flutter: '>=3.19.0' dependencies: flutter: sdk: flutter flutter_riverpod: usecase: path: ../usecase dev_dependencies: flutter_test: sdk: flutter 6TF$BTFύοέʔδΛ ґଘͱͯ͠௥Ճ͢Δ

Slide 36

Slide 36 text

ΞϓϦຊମ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; // import '../state/user.dart'; import 'package:usecase/usecase.dart'; class UserName extends ConsumerWidget { const UserName({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final userState = ref.watch(userStateNotifierProvider); return userState.when( data: (state) { return Text('User name: ${state.user?.name}'); }, error: (error, _) { return Text('Error: $error'); }, loading: () { return const CircularProgressIndicator(); }, ); } VTFDBTFύοέʔδΛ JNQPSU͢Δ VTFDBTFύοέʔδͷ 4UBUFΛࢀরͰ͖Δ

Slide 37

Slide 37 text

App Gateway UseCase UseCase Gateway App ґଘ͚ͩΛٯసͤ͞Δ

Slide 38

Slide 38 text

UseCase ύοέʔδͷґଘ name: usecase description: UseCase for domain Layer environment: sdk: '>=3.3.0 <4.0.0' dependencies: riverpod_annotation: # gateway: # path: ../gateway dev_dependencies: build_runner: riverpod_generator: HBUFXBZύοέʔδΛ ґଘ͔Β࡟আ͢Δ

Slide 39

Slide 39 text

UseCase ύοέʔδͷґଘ import 'package:riverpod_annotation/riverpod_annotation.dart'; // import '../repository/user.dart'; import 'package:gateway/gateway.dart'; part 'user.g.dart'; @riverpod class UserStateNotifier extends _$UserStateNotifier { @override FutureOr build() async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } } HBUFXBZύοέʔδ͕ JNQPSUͰ͖ͳ͘ͳΔ HBUFXBZύοέʔδͷ 3FQPTJUPSZΛࢀরͰ͖ͳ͍

Slide 40

Slide 40 text

UseCase ύοέʔδͷґଘ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'user.g.dart'; @riverpod UserRepository userRepository(_) => throw UnimplementedError(); abstract interface class UserRepository { Future getUser(); } ͻͱ·ͣ&SSPSΛUISPX ͓ͯ͘͠

Slide 41

Slide 41 text

UseCase ύοέʔδͷґଘ import 'package:riverpod_annotation/riverpod_annotation.dart'; // import 'package:gateway/gateway.dart'; import '../repository/user.dart'; part 'user.g.dart'; @riverpod class UserStateNotifier extends _$UserStateNotifier { @override FutureOr build() async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } } VTFDBTF಺ͷϑΝΠϧΛ JNQPSU͢Δ VTFDBTFύοέʔδͷ 3FQPTJUPSZΛࢀরͰ͖Δ

Slide 42

Slide 42 text

ΞϓϦ࣮ߦ UseCase ಺ͷ UserRepository ͷΠϯλʔ ϑΣʔε͕ throw ͨ͠Τ ϥʔ͕දࣔ͞ΕΔ

Slide 43

Slide 43 text

ΞϓϦ࣮ߦ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'user.g.dart'; @riverpod UserRepository userRepository(_) => throw UnimplementedError(); abstract interface class UserRepository { Future getUser(); } ౰વ͍͕ͭ͜දࣔ͞ΕΔʂ

Slide 44

Slide 44 text

riverpod ʹΑΔґଘղܾ • overrideWith ʢಈతɺ ref ͋Γʣ • overrideWithValue ʢ੩తɺ ref ͳ͠ʣ • ProviderScope Ͱ্ॻ͖ͷઃఆ

Slide 45

Slide 45 text

Gateway ύοέʔδͷ஫ೖ name: gateway description: Gateway for infrastructure Layer environment: sdk: '>=3.3.0 <4.0.0' dependencies: riverpod_annotation: usecase: path: ../usecase dev_dependencies: build_runner: riverpod_generator: VTFDBTFύοέʔδΛ ґଘʹ௥Ճ͢Δ

Slide 46

Slide 46 text

Gateway ύοέʔδͷ஫ೖ import 'package:usecase/usecase.dart'; final userRepositoryOverride = userRepositoryProvider.overrideWith(UserRepositoryImpl.new); class UserRepositoryImpl implements UserRepository { const UserRepositoryImpl(this.ref); final UserRepositoryRef ref; Future getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User(response.id, response.name); } } VTFDBTFύοέʔδͷ 3FQPTJUPSZΛ্ॻ͖͢Δ VTFDBTFύοέʔδͷ 3FQPTJUPSZΛܧঝ͢Δ

Slide 47

Slide 47 text

Gateway ύοέʔδͷ஫ೖ name: app description: Application environment: sdk: '>=3.3.0 <4.0.0' flutter: '>=3.19.0' dependencies: flutter: sdk: flutter flutter_riverpod: gateway: path: ../gateway usecase: path: ../usecase dev_dependencies: flutter_test: sdk: flutter HBUFXBZύοέʔδΛ ґଘͱͯ͠௥Ճ͢Δ

Slide 48

Slide 48 text

Gateway ύοέʔδͷ஫ೖ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gateway/gateway.dart'; class UserPage extends StatelessWidget { const UserPage({super.key}); @override Widget build(BuildContext context) { return ProviderScope( overrides: [ userRepositoryOverride, ], child: const UserName(), ); } } HBUFXBZύοέʔδΛ JNQPSU͢Δ 3FQPTJUPSZΛ্ॻ͖͢Δ είʔϓΛઃఆ͢Δ

Slide 49

Slide 49 text

ΞϓϦ࣮ߦ Gateway ಺ͷ UserRepository ͕ฦͨ͠ Ϣʔβ໊͕දࣔ͞ΕΔΑ͏ ʹͳΔ

Slide 50

Slide 50 text

Gateway σʔλͷྲྀΕ App UseCase

Slide 51

Slide 51 text

UseCase Gateway App ґଘͷํ޲

Slide 52

Slide 52 text

ύοέʔδߏ੒ App Gateway UseCase

Slide 53

Slide 53 text

ґଘΛٯసͤ͞ΔͨΊʹ΍Δ͜ͱ • ٯస͍ͤͨ͞ґଘॲཧͷΠϯλʔϑΣʔεΛఆٛ → UseCase • ఆٛͨ͠ΠϯλʔϑΣʔεΛܧঝͯ͠ґଘॲཧΛ࣮૷ → Gateway • ࣮૷ͨ͠ґଘॲཧΛ஫ೖ → App

Slide 54

Slide 54 text

ProviderScope ͷઃఆͷ໰୊఺ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gateway/gateway.dart'; class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return ProviderScope( overrides: [ artistRepositoryOverride, authRepositoryOverride, notificationRepositoryOverride, searchRepositoryOverride, userRepositoryOverride, userProfileRepositoryOverride, ], child: const Page(), ); } } ্ॻ͖ͷ਺͚ͩม਺Λઃఆ ͢Δඞཁ͕͋Δ

Slide 55

Slide 55 text

https://github.com/KyoheiG3/ override_pod override_pod

Slide 56

Slide 56 text

override_pod • ύοέʔδ಺ͷ Override ม਺ΛूΊͯ഑ྻͷม਺Λ࡞੒ • ΞϊςʔγϣϯͱδΣωϨʔλͷ 2ͭͷύοέʔδ

Slide 57

Slide 57 text

override_pod name: gateway description: Gateway for infrastructure Layer environment: sdk: '>=3.3.0 <4.0.0' dependencies: riverpod_annotation: usecase: path: ../usecase override_pod_annotation: dev_dependencies: build_runner: riverpod_generator: override_pod_generator: BOOPUBUJPOΛ௥Ճ͢Δ HFOFSBUPSΛ௥Ճ͢Δ

Slide 58

Slide 58 text

Before import 'package:usecase/usecase.dart'; final userRepositoryOverride = userRepositoryProvider.overrideWith(UserRepositoryImpl.new); class UserRepositoryImpl implements UserRepository { const UserRepositoryImpl(this.ref); final UserRepositoryRef ref; Future getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User(response.id, response.name); } }

Slide 59

Slide 59 text

After import 'package:override_pod_annotation/override_pod_annotation.dart'; import 'package:usecase/usecase.dart'; @overridePod final pod = userRepositoryProvider.overrideWith(UserRepositoryImpl.new); class UserRepositoryImpl implements UserRepository { const UserRepositoryImpl(this.ref); final UserRepositoryRef ref; Future getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User(response.id, response.name); } } 0WFSSJEFͷม਺໊͸ࣗ༝ !PWFSSJEF1PEΛ෇͚Δ Πϯϙʔτ͢Δ

Slide 60

Slide 60 text

Before import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gateway/gateway.dart'; class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return ProviderScope( overrides: [ artistRepositoryOverride, authRepositoryOverride, notificationRepositoryOverride, searchRepositoryOverride, userRepositoryOverride, userProfileRepositoryOverride, ], child: const Page(), ); } } શͯͷม਺Λઃఆ͍ͯͨ͠

Slide 61

Slide 61 text

After import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gateway/gateway.dart'; class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return ProviderScope( overrides: gatewayPodOverrides, child: const Page(), ); } } ग़དྷ্͕ͬͨม਺Λ ઃఆ͢Δ͚ͩ

Slide 62

Slide 62 text

ΞδΣϯμ •֓ཁ •ύοέʔδ෼ׂͷ࣮૷ྫ •ύοέʔδͷ໾ׂ •ςετʹ͍ͭͯ

Slide 63

Slide 63 text

App Gateway UseCase App UI Driver Presenter Gateway Entity UseCase ໾ׂ͝ͱʹύοέʔδΛ෼͚Δ

Slide 64

Slide 64 text

• ΦϒδΣΫτ • ڞ௨ͷ஌ࣝΛදͨ͠΋ͷ Entity

Slide 65

Slide 65 text

Entity class User { const User({ required this.id, required this.name, required this.createdAt, }); final int id; final String name; final String createdAt; }

Slide 66

Slide 66 text

• Repository ͷΠϯλʔϑΣʔεΛఆٛ • ෳ਺ͷ Repository ͷॲཧΛ࣮ߦ • Ϣʔβ΍ΞϓϦͷঢ়ଶΛ AsyncNoti fi er Ͱอ࣋ UseCase

Slide 67

Slide 67 text

UseCase @riverpod class UserStateNotifier extends _$UserStateNotifier { @override FutureOr build() async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } Future logout() async { await ref.read(userRepositoryProvider).logout(); state = const AsyncValue.data( UserState( user: null, loggedIn: false, ), ); } }

Slide 68

Slide 68 text

• UseCase ʹ௚઀ґଘ • Driver ͷΠϯλʔϑΣʔεΛఆٛ • Repository ͷ۩ମతͳॲཧΛ࣮૷ Gateway

Slide 69

Slide 69 text

Gateway @overridePod final pod = userRepositoryProvider.overrideWith(UserRepositoryImpl.new); class UserRepositoryImpl implements UserRepository { const UserRepositoryImpl(this.ref); final UserRepositoryRef ref; Future getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User( id: response.id, name: response.name, createdAt: response.createdAt, ); } Future logout() async { await ref.read(dioProvider).user.logout(); } }

Slide 70

Slide 70 text

Gateway @overridePod final pod = locationRepositoryProvider.overrideWith(LocationRepositoryImpl.new); class LocationRepositoryImpl implements LocationRepository { const LocationRepositoryImpl(this.ref); final LocationRepositoryRef ref; @override Future requestPermission() { return ref.read(locationProvider).requestPermission(); } @override Stream get position { return ref.read(locationProvider).getPositionStream(distanceFilter: 10); } }

Slide 71

Slide 71 text

• Gateway ʹ௚઀ґଘ • Driver ͷ۩ମతͳॲཧͷ࣮૷ • σόΠεʹґଘͨ͠ύοέʔδΛϥοϓ Driver

Slide 72

Slide 72 text

Driver import 'package:geolocator/geolocator.dart' as geolocator; @overridePod final pod = locationProvider.overrideWithValue(LocationImpl()); class LocationImpl implements Location { @override Future requestPermission() { return geolocator.Geolocator.requestPermission() .then((value) => value.toLocationPermission()); } @override Stream getPositionStream({ required int distanceFilter, }) { return geolocator.Geolocator.getPosition( locationSettings: locator.LocationSettings( distanceFilter: distanceFilter, ), ).map((event) => event.toLocationPosition()); } }

Slide 73

Slide 73 text

Presenter (Controller) • UseCase ʹ௚઀ґଘ • Repository ͷॲཧ͸ݺͼग़͞ͳ͍ • ෳ਺ͷ State ͔Β ViewModel Λ࡞੒ͯ͠ UI ʹฦ͢ • UI ͔ΒͷೖྗΛड͚෇͚Δ

Slide 74

Slide 74 text

Presenter (Controller) @riverpod Future userPageController(UserPageControllerRef ref) async { final userState = await ref.watch(userStateNotifierProvider.future); return UserPageModel( userName: userState.currentUser.name, createdAt: userState.currentUser.createdAt, action: UserPageAction( logout: () async { await ref.read(userStateNotifierProvider.notifier).logout(); }, ), ); }

Slide 75

Slide 75 text

UI • Presenter ʹ௚઀ґଘ • ViewModel Λड͚औͬͯ Widget Ͱදࣔ • Ϣʔβͷૢ࡞Λ Controller ʹ఻͑Δ

Slide 76

Slide 76 text

UI class UserPage extends StatelessWidget { const UserPage({ super.key, required this.model, }); final UserPageModel model; @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ Text(model.userName), Text(model.createdAt), ElevatedButton( onPressed: model.action.logout, child: const Text('Logout'), ), ], ), ); } }

Slide 77

Slide 77 text

App • Gateway / Driver / UI ʹ௚઀ґଘ • ্ॻ͖ͨ͠ґଘΛ ProviderScope Ͱ஫ೖ

Slide 78

Slide 78 text

App import 'package:driver/driver.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gateway/gateway.dart'; import 'package:ui/ui.dart'; void main() { runApp( ProviderScope( overrides: [ ...driverPodOverrides, ...gatewayPodOverrides, ], child: const App(), ), ); }

Slide 79

Slide 79 text

Driver Gateway UseCase Presenter UI ॲཧͷྲྀΕ

Slide 80

Slide 80 text

ґଘͷํ޲ UI UseCase Gateway Presenter Driver App

Slide 81

Slide 81 text

App UI Driver Presenter Gateway Entity UseCase ύοέʔδߏ੒

Slide 82

Slide 82 text

ΫϦʔϯΞʔΩςΫνϟ 'SBNFXPSL΍%FWJDF ʹؔΘΔ෦෼

Slide 83

Slide 83 text

Flutter ΍σόΠεʹґଘ͍ͯ͠Δύοέʔδ App UI Driver

Slide 84

Slide 84 text

Presenter Gateway Entity UseCase Flutter ΍σόΠεʹґଘ͍ͯ͠ͳ͍ύοέʔδ

Slide 85

Slide 85 text

App UI Driver Presenter Gateway Entity UseCase ύοέʔδߏ੒

Slide 86

Slide 86 text

No content

Slide 87

Slide 87 text

Melos • ઃఆͨ͠σΟϨΫτϦ಺ͷ pubspec.yaml Λ୳ͯ͠ղੳ • pubspec_overrides.yaml Λࣗಈੜ੒ͯ͠ϩʔΧϧͷύοέʔδͷґଘΛղܾ • λεΫͷฒྻॲཧ

Slide 88

Slide 88 text

ϩʔΧϧͷύοέʔδͷґଘΛղܾ name: app packages: - packages/**

Slide 89

Slide 89 text

ϩʔΧϧͷύοέʔδͷґଘΛղܾ $ melos bootstrap

Slide 90

Slide 90 text

ϩʔΧϧͷύοέʔδͷґଘΛղܾ # melos_managed_dependency_overrides: usecase dependency_overrides: usecase: path: ../usecase

Slide 91

Slide 91 text

ϩʔΧϧͷύοέʔδͷґଘΛղܾ name: gateway description: Gateway for infrastructure Layer environment: sdk: '>=3.3.0 <4.0.0' dependencies: riverpod_annotation: usecase: # path: ../usecase dev_dependencies: build_runner: riverpod_generator: VTFDBTFύοέʔδͷ QBUIΛলུͰ͖Δ

Slide 92

Slide 92 text

λεΫͷฒྻॲཧ • concurrency Ͱฒྻ࣮ߦ਺ΛมߋՄೳ • orderDependents Ͱύοέʔδͷґଘؔ܎άϥϑʹج͍ͮͯෳ਺ͷύοέʔ δͰεΫϦϓτΛ࣮ߦՄೳ • packageFilters ʹ౰ͯ͸·Δύοέʔδશͯʹରͯ͠ฒྻͰλεΫΛ࣮ߦ͠ ͯ͘ΕΔ

Slide 93

Slide 93 text

λεΫͷฒྻॲཧ name: app packages: - packages/** scripts: gen: run: melos gen:all --no-select gen:all: run: dart run build_runner build --delete-conflicting-outputs exec: concurrency: 8 orderDependents: true packageFilters: dependsOn: build_runner

Slide 94

Slide 94 text

ΞδΣϯμ •֓ཁ •ύοέʔδ෼ׂͷ࣮૷ྫ •ύοέʔδͷ໾ׂ •ςετʹ͍ͭͯ

Slide 95

Slide 95 text

fl utter test --coverage --update-goldens • --coverage • ςετͷΧόϨοδΛ lcov.info ϑΝΠϧʹग़ྗ͢Δ • --update-goldens • Golden Test ͷߋ৽༻ͷը૾ग़ྗ΋ಉ࣌ʹߦ͏

Slide 96

Slide 96 text

Melos Λ࢖ͬͨϢχοτςετͷ࣮ߦ name: app packages: - packages/** scripts: test: run: melos test:all --no-select test:all: run: flutter test --coverage --update-goldens exec: concurrency: 8 packageFilters: dirExists: - test UFTUσΟϨΫτϦ͕͋Δ ύοέʔδ͚࣮ͩߦ͢Δ

Slide 97

Slide 97 text

ϚϧνύοέʔδͷςετͷΧόϨοδ • ෳ਺ύοέʔδͰಉ͡ύεͷϑΝΠϧ͸ΧόϨοδ͕߹ࢉ͞ΕͨΓ͢Δ • ΠϯλʔϑΣʔεͷΈͷΧόϨοδͷܭଌ͸ɺΤϥʔΛ throw ͍ͯ͠Δ͚ͩ ͩͬͨΓɺ࣮ࡍʹ͸ॲཧ͕ແ͍ͷͰෆཁ

Slide 98

Slide 98 text

https://github.com/KyoheiG3/ lcov_excluder lcov_excluder

Slide 99

Slide 99 text

lcov_excluder • ΧόϨοδʹؚ·ΕΔෆཁͳܭଌϑΝΠϧΛআ֎ • আ֎ઃఆ͢Δ lcov.info Λɺ೚ҙͷ .yaml ʹఆ࣮ٛͯ͠ߦ

Slide 100

Slide 100 text

ΧόϨοδܭଌͷআ֎ઃఆྫ coverage: default: exclude: - lib/**/*.*.dart targets: - usecase: sourceRoot: packages/usecase exclude: - lib/src/repository/*

Slide 101

Slide 101 text

ΧόϨοδܭଌͷআ֎ઃఆྫ $ dart run lcov_excluder exclude

Slide 102

Slide 102 text

·ͱΊ • ύοέʔδΛ෼͚Δ৔߹͸໾ׂΛҙࣝ͢Δ • Flutter ΍ Device ͳͲӨڹͷେ͖ͳ΋ͷ΄Ͳԁͷ֎ଆʹஔ͘Α͏ʹ͢Δ • Өڹͷখ͞ͳ΋ͷ͸ԁͷ಺ଆʹஔ͘Α͏ʹ͢Δ • ςετͳͲ͸ฒྻͰ࣮ߦ͢Δ • ΠϯλʔϑΣʔεͷςετͷΧόϨοδܭଌ͸ߦΘͳ͍

Slide 103

Slide 103 text

Thanks !