Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
flutterkaigi_2024.pdf
Search
Kyohei Ito
November 21, 2024
Programming
1.7k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
flutterkaigi_2024.pdf
Kyohei Ito
November 21, 2024
More Decks by Kyohei Ito
See All by Kyohei Ito
flutter_kaigi_2025.pdf
kyoheig3
2
1k
layerx_20241129.pdf
kyoheig3
2
510
flutter_kaigi_2021.pdf
kyoheig3
0
1.2k
flutter_kmm_1.pdf
kyoheig3
1
1.2k
ca.swift_10.pdf
kyoheig3
0
720
iosdc_2018.pdf
kyoheig3
2
3.2k
orecon_vol1.pdf
kyoheig3
4
1.7k
iosdc_2017.pdf
kyoheig3
4
940
ca.swift_2.pdf
kyoheig3
9
1.4k
Other Decks in Programming
See All in Programming
Modding RubyKaigi for Myself
yui_knk
0
910
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
330
CLIであることを活かしたGitHub Copilot CLI活用術 / GitHub Copilot CLI Pro Tips & Tricks
nao_mk2
1
1.2k
Technical Debt: Understanding it Rightly, Engaging it Rightly #LaravelLiveJP
shogogg
0
210
AutonomyとControlのあいだ:Graflowで記述するAIエージェント協調
myui
0
110
不変条件と整合性境界—ビジネスが決める設計判断と実現パターン / Invariants and Consistency Boundaries
nrslib
13
3.6k
技術記事、AIに書かせるか、自分で書くか? 〜それでも私が自分の手で書く理由〜 / #QiitaConference
jnchito
2
1.3k
肥大化するレガシーコードに立ち向かうためのインターフェース分離と依存の逆転 / JJUG CCC 2026 Spring
hirokunimaeta
0
530
AI時代の仕事技芸論 — ソフトウェア開発で「遊ぶように働く」職人的熟達のすすめ
kuranuki
1
640
3Dシーンの圧縮
fadis
1
680
生成AI時代にこそ効くGo | Why Go Works in the Age of Generative AI
mom0tomo
8
3.2k
Vite+ Unified Toolchain for the Web
naokihaba
0
220
Featured
See All Featured
Building a Scalable Design System with Sketch
lauravandoore
463
34k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
600
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
55k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
200
Code Reviewing Like a Champion
maltzj
528
40k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
610
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.5k
Documentation Writing (for coders)
carmenintech
77
5.4k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
330
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
290
Measuring & Analyzing Core Web Vitals
bluesmoon
9
860
Transcript
࣮ફతύοέʔδઓུ FlutterKaigi 2024
About Me ҏ౻ ګฏ גࣜձࣾαΠόʔΤʔδΣϯτ Github: KyoheiG3 X: KyoheiG3
ΞδΣϯμ •֓ཁ •ύοέʔδׂͷ࣮ྫ •ύοέʔδͷׂ •ςετʹ͍ͭͯ
ΞδΣϯμ •֓ཁ •ύοέʔδׂͷ࣮ྫ •ύοέʔδͷׂ •ςετʹ͍ͭͯ
ύοέʔδׂͷύλʔϯྫ • ΞϓϦέʔγϣϯͱ cli ͳͲͷπʔϧ • ௨৴෦ͳͲͷ෦୯Ґ • ෳͷΞϓϦέʔγϣϯͱڞ௨ػೳ •
ػೳ୯Ґ • ϨΠϠʔ • ͚ͳ͍
ΞϓϦΛී௨ʹ։ൃ͢Δͱ ͜ͷܗʹͳΔ App ͚ͳ͍
෦୯ҐͰ͚Δ Γग़͞Εͨ෦ͷ୯ମς ετ͕Γ͘͢ͳΔ Network Location DB App
ڞ௨ػೳͷΓग़͠ Core ػೳΛڞ༗͢ΔΞϓϦ ͕૿͑Δͱෆཁͳػೳ͕૿ ͑Δ Core App
ػೳ୯ҐͰ͚Δ ଞͷػೳͷӨڹΛ͋·Γ ߟ͑ͳ͍͍ͯ͘໘ɺػೳ ಉ࢜ͷ࿈ܞ͕͍͠ App Core Noti fi cation Purchase
Login
ϨΠϠʔͰ͚Δ ֎ଆͷϨΠϠʔଆͷϨ ΠϠʔʹґଘ͍ͯ͠Δ Presentation App Application Infra Domain
ΫϦʔϯΞʔΩςΫνϟ ը૾ग़లɿThe Clean Code Blog by Robert C. Martin (Uncle
Bob) | The Clean Architecture
ΫϦʔϯΞʔΩςΫνϟ ґଘੑٯస App UI Driver Presenter Gateway Entity UseCase
• Ͳ͜ͰԿΛॲཧ͢Δ͔͕໌֬ʹͳ࣮ͬͯʹڧ੍ྗ͕ੜ·ΕΔ • DI ͕ඞਢͳͷͰࣗͣͱϢχοτςετͷΈ͕͏ • ςετͷࡍʹෆཁͳύοέʔδΛഉআͰ͖Δ • ύοέʔδͷॲཧΛฒྻʹߦ͑ΔΑ͏ʹͳΔ ׂ࣌ͷϝϦοτ
ׂ࣌ͷσϝϦοτ • ׳ΕΔ·Ͱࠞཚ͕ͪ͠ • ΠϯλʔϑΣʔεͳͲͷهड़͕૿͑Δ • ίʔυδϟϯϓ͕໘ʹͳΔ
ΞδΣϯμ •֓ཁ •ύοέʔδׂͷ࣮ྫ •ύοέʔδͷׂ •ςετʹ͍ͭͯ
None
riverpod • ΞϓϦશମͷঢ়ଶཧ • ґଘͷೖ͕༰қʢDIʣ • ϥϯλΠϜͰͳͯ͘ίϯύΠϧλΠϜͰܕͷΤϥʔ͕͔Δʢܕ҆શʣ
Ϣʔβ໊Λදࣔ͢Δ͚ͩͷ ΞϓϦΛ࣮ͯ͠ΈΔ • Repository ܦ༝Ͱσʔλ औಘ • UseCase ͰσʔλΛՃ •
UI ʹදࣔ
App ύοέʔδͳͲಛʹߟ͑ͣʹ࡞Δ
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<User?> getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User(response.id, response.name); } }
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<UserState> build() async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } }
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(); }, ); } }
ΞϓϦ࣮ߦ UserRepository ͕ฦͨ͠ Ϣʔβ໊͕දࣔ͞ΕΔ
App App ϨΠϠʔ͝ͱʹύοέʔδΛ͚Δ
ϨΠϠʔͷྫ • ΠϯϑϥϨΠϠʔ • DB API ʹΞΫηεͨ͠Γ͢Δ • Repository
ͳͲ • ΞϓϦέʔγϣϯϨΠϠʔ • ϏδωεϩδοΫ͕ू͞ΕΔ • UseCase ͳͲ
ΫϦʔϯΞʔΩςΫνϟ ͜ͷ͋ͨΓΛࢀߟʹ ύοέʔδΛ໋໊
ύοέʔδͷྫ • ΠϯϑϥϨΠϠʔ • Gateway ύοέʔδΛ࡞ • UserRepository ΛҠಈ •
ΞϓϦέʔγϣϯϨΠϠʔ • UseCase ύοέʔδΛ࡞ • UserStateNoti fi er ΛҠಈ
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
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:
Gateway ύοέʔδͷ࡞ export 'src/repository/user.dart';
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
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ύοέʔδΛ ґଘͱͯ͠Ճ͢Δ
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<UserState> build() async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } } HBUFXBZύοέʔδΛ JNQPSU͢Δ HBUFXBZύοέʔδͷ 3FQPTJUPSZΛࢀরͰ͖Δ
UseCase ύοέʔδͷ࡞ export 'src/state/user.dart';
ΞϓϦຊମ 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ύοέʔδΛ ґଘͱͯ͠Ճ͢Δ
ΞϓϦຊମ 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ΛࢀরͰ͖Δ
App Gateway UseCase UseCase Gateway App ґଘ͚ͩΛٯసͤ͞Δ
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ύοέʔδΛ ґଘ͔Βআ͢Δ
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<UserState> build() async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } } HBUFXBZύοέʔδ͕ JNQPSUͰ͖ͳ͘ͳΔ HBUFXBZύοέʔδͷ 3FQPTJUPSZΛࢀরͰ͖ͳ͍
UseCase ύοέʔδͷґଘ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'user.g.dart'; @riverpod UserRepository userRepository(_) =>
throw UnimplementedError(); abstract interface class UserRepository { Future<User?> getUser(); } ͻͱ·ͣ&SSPSΛUISPX ͓ͯ͘͠
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<UserState> build() async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } } VTFDBTFͷϑΝΠϧΛ JNQPSU͢Δ VTFDBTFύοέʔδͷ 3FQPTJUPSZΛࢀরͰ͖Δ
ΞϓϦ࣮ߦ UseCase ͷ UserRepository ͷΠϯλʔ ϑΣʔε͕ throw ͨ͠Τ ϥʔ͕දࣔ͞ΕΔ
ΞϓϦ࣮ߦ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'user.g.dart'; @riverpod UserRepository userRepository(_) => throw
UnimplementedError(); abstract interface class UserRepository { Future<User?> getUser(); } વ͍͕ͭ͜දࣔ͞ΕΔʂ
riverpod ʹΑΔґଘղܾ • overrideWith ʢಈతɺ ref ͋Γʣ • overrideWithValue ʢ੩తɺ
ref ͳ͠ʣ • ProviderScope Ͱ্ॻ͖ͷઃఆ
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ύοέʔδΛ ґଘʹՃ͢Δ
Gateway ύοέʔδͷೖ import 'package:usecase/usecase.dart'; final userRepositoryOverride = userRepositoryProvider.overrideWith(UserRepositoryImpl.new); class UserRepositoryImpl
implements UserRepository { const UserRepositoryImpl(this.ref); final UserRepositoryRef ref; Future<User?> getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User(response.id, response.name); } } VTFDBTFύοέʔδͷ 3FQPTJUPSZΛ্ॻ͖͢Δ VTFDBTFύοέʔδͷ 3FQPTJUPSZΛܧঝ͢Δ
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ύοέʔδΛ ґଘͱͯ͠Ճ͢Δ
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Λ্ॻ͖͢Δ είʔϓΛઃఆ͢Δ
ΞϓϦ࣮ߦ Gateway ͷ UserRepository ͕ฦͨ͠ Ϣʔβ໊͕දࣔ͞ΕΔΑ͏ ʹͳΔ
Gateway σʔλͷྲྀΕ App UseCase
UseCase Gateway App ґଘͷํ
ύοέʔδߏ App Gateway UseCase
ґଘΛٯసͤ͞ΔͨΊʹΔ͜ͱ • ٯస͍ͤͨ͞ґଘॲཧͷΠϯλʔϑΣʔεΛఆٛ → UseCase • ఆٛͨ͠ΠϯλʔϑΣʔεΛܧঝͯ͠ґଘॲཧΛ࣮ → Gateway •
࣮ͨ͠ґଘॲཧΛೖ → App
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(), ); } } ্ॻ͖ͷ͚ͩมΛઃఆ ͢Δඞཁ͕͋Δ
https://github.com/KyoheiG3/ override_pod override_pod
override_pod • ύοέʔδͷ Override มΛूΊͯྻͷมΛ࡞ • ΞϊςʔγϣϯͱδΣωϨʔλͷ 2ͭͷύοέʔδ
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ΛՃ͢Δ
Before import 'package:usecase/usecase.dart'; final userRepositoryOverride = userRepositoryProvider.overrideWith(UserRepositoryImpl.new); class UserRepositoryImpl implements
UserRepository { const UserRepositoryImpl(this.ref); final UserRepositoryRef ref; Future<User?> getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User(response.id, response.name); } }
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<User?> getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User(response.id, response.name); } } 0WFSSJEFͷม໊ࣗ༝ !PWFSSJEF1PEΛ͚Δ Πϯϙʔτ͢Δ
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(), ); } } શͯͷมΛઃఆ͍ͯͨ͠
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(), ); } } ग़དྷ্͕ͬͨมΛ ઃఆ͢Δ͚ͩ
ΞδΣϯμ •֓ཁ •ύοέʔδׂͷ࣮ྫ •ύοέʔδͷׂ •ςετʹ͍ͭͯ
App Gateway UseCase App UI Driver Presenter Gateway Entity UseCase
ׂ͝ͱʹύοέʔδΛ͚Δ
• ΦϒδΣΫτ • ڞ௨ͷࣝΛදͨ͠ͷ Entity
Entity class User { const User({ required this.id, required this.name,
required this.createdAt, }); final int id; final String name; final String createdAt; }
• Repository ͷΠϯλʔϑΣʔεΛఆٛ • ෳͷ Repository ͷॲཧΛ࣮ߦ • ϢʔβΞϓϦͷঢ়ଶΛ AsyncNoti
fi er Ͱอ࣋ UseCase
UseCase @riverpod class UserStateNotifier extends _$UserStateNotifier { @override FutureOr<UserState> build()
async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } Future<void> logout() async { await ref.read(userRepositoryProvider).logout(); state = const AsyncValue.data( UserState( user: null, loggedIn: false, ), ); } }
• UseCase ʹґଘ • Driver ͷΠϯλʔϑΣʔεΛఆٛ • Repository ͷ۩ମతͳॲཧΛ࣮ Gateway
Gateway @overridePod final pod = userRepositoryProvider.overrideWith(UserRepositoryImpl.new); class UserRepositoryImpl implements UserRepository
{ const UserRepositoryImpl(this.ref); final UserRepositoryRef ref; Future<User?> getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User( id: response.id, name: response.name, createdAt: response.createdAt, ); } Future<void> logout() async { await ref.read(dioProvider).user.logout(); } }
Gateway @overridePod final pod = locationRepositoryProvider.overrideWith(LocationRepositoryImpl.new); class LocationRepositoryImpl implements LocationRepository
{ const LocationRepositoryImpl(this.ref); final LocationRepositoryRef ref; @override Future<LocationPermission> requestPermission() { return ref.read(locationProvider).requestPermission(); } @override Stream<LocationPosition> get position { return ref.read(locationProvider).getPositionStream(distanceFilter: 10); } }
• Gateway ʹґଘ • Driver ͷ۩ମతͳॲཧͷ࣮ • σόΠεʹґଘͨ͠ύοέʔδΛϥοϓ Driver
Driver import 'package:geolocator/geolocator.dart' as geolocator; @overridePod final pod = locationProvider.overrideWithValue(LocationImpl());
class LocationImpl implements Location { @override Future<LocationPermission> requestPermission() { return geolocator.Geolocator.requestPermission() .then((value) => value.toLocationPermission()); } @override Stream<LocationPosition> getPositionStream({ required int distanceFilter, }) { return geolocator.Geolocator.getPosition( locationSettings: locator.LocationSettings( distanceFilter: distanceFilter, ), ).map((event) => event.toLocationPosition()); } }
Presenter (Controller) • UseCase ʹґଘ • Repository ͷॲཧݺͼग़͞ͳ͍ • ෳͷ
State ͔Β ViewModel Λ࡞ͯ͠ UI ʹฦ͢ • UI ͔ΒͷೖྗΛड͚͚Δ
Presenter (Controller) @riverpod Future<UserPageModel> 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(); }, ), ); }
UI • Presenter ʹґଘ • ViewModel Λड͚औͬͯ Widget Ͱදࣔ •
Ϣʔβͷૢ࡞Λ Controller ʹ͑Δ
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'), ), ], ), ); } }
App • Gateway / Driver / UI ʹґଘ • ্ॻ͖ͨ͠ґଘΛ
ProviderScope Ͱೖ
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(), ), ); }
Driver Gateway UseCase Presenter UI ॲཧͷྲྀΕ
ґଘͷํ UI UseCase Gateway Presenter Driver App
App UI Driver Presenter Gateway Entity UseCase ύοέʔδߏ
ΫϦʔϯΞʔΩςΫνϟ 'SBNFXPSL%FWJDF ʹؔΘΔ෦
Flutter σόΠεʹґଘ͍ͯ͠Δύοέʔδ App UI Driver
Presenter Gateway Entity UseCase Flutter σόΠεʹґଘ͍ͯ͠ͳ͍ύοέʔδ
App UI Driver Presenter Gateway Entity UseCase ύοέʔδߏ
None
Melos • ઃఆͨ͠σΟϨΫτϦͷ pubspec.yaml Λ୳ͯ͠ղੳ • pubspec_overrides.yaml Λࣗಈੜͯ͠ϩʔΧϧͷύοέʔδͷґଘΛղܾ • λεΫͷฒྻॲཧ
ϩʔΧϧͷύοέʔδͷґଘΛղܾ name: app packages: - packages/**
ϩʔΧϧͷύοέʔδͷґଘΛղܾ $ melos bootstrap
ϩʔΧϧͷύοέʔδͷґଘΛղܾ # melos_managed_dependency_overrides: usecase dependency_overrides: usecase: path: ../usecase
ϩʔΧϧͷύοέʔδͷґଘΛղܾ 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ΛলུͰ͖Δ
λεΫͷฒྻॲཧ • concurrency Ͱฒྻ࣮ߦΛมߋՄೳ • orderDependents Ͱύοέʔδͷґଘؔάϥϑʹج͍ͮͯෳͷύοέʔ δͰεΫϦϓτΛ࣮ߦՄೳ • packageFilters
ʹͯ·Δύοέʔδશͯʹରͯ͠ฒྻͰλεΫΛ࣮ߦ͠ ͯ͘ΕΔ
λεΫͷฒྻॲཧ 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
ΞδΣϯμ •֓ཁ •ύοέʔδׂͷ࣮ྫ •ύοέʔδͷׂ •ςετʹ͍ͭͯ
fl utter test --coverage --update-goldens • --coverage • ςετͷΧόϨοδΛ lcov.info
ϑΝΠϧʹग़ྗ͢Δ • --update-goldens • Golden Test ͷߋ৽༻ͷը૾ग़ྗಉ࣌ʹߦ͏
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σΟϨΫτϦ͕͋Δ ύοέʔδ͚࣮ͩߦ͢Δ
ϚϧνύοέʔδͷςετͷΧόϨοδ • ෳύοέʔδͰಉ͡ύεͷϑΝΠϧΧόϨοδ͕߹ࢉ͞ΕͨΓ͢Δ • ΠϯλʔϑΣʔεͷΈͷΧόϨοδͷܭଌɺΤϥʔΛ throw ͍ͯ͠Δ͚ͩ ͩͬͨΓɺ࣮ࡍʹॲཧ͕ແ͍ͷͰෆཁ
https://github.com/KyoheiG3/ lcov_excluder lcov_excluder
lcov_excluder • ΧόϨοδʹؚ·ΕΔෆཁͳܭଌϑΝΠϧΛআ֎ • আ֎ઃఆ͢Δ lcov.info Λɺҙͷ .yaml ʹఆ࣮ٛͯ͠ߦ
ΧόϨοδܭଌͷআ֎ઃఆྫ coverage: default: exclude: - lib/**/*.*.dart targets: - usecase: sourceRoot:
packages/usecase exclude: - lib/src/repository/*
ΧόϨοδܭଌͷআ֎ઃఆྫ $ dart run lcov_excluder exclude
·ͱΊ • ύοέʔδΛ͚Δ߹ׂΛҙࣝ͢Δ • Flutter Device ͳͲӨڹͷେ͖ͳͷ΄Ͳԁͷ֎ଆʹஔ͘Α͏ʹ͢Δ • Өڹͷখ͞ͳͷԁͷଆʹஔ͘Α͏ʹ͢Δ
• ςετͳͲฒྻͰ࣮ߦ͢Δ • ΠϯλʔϑΣʔεͷςετͷΧόϨοδܭଌߦΘͳ͍
Thanks !