Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AndroidやiOSのアプリ開発でいつも私がやっていることをFlutterでやってみた / Flutter development as usual

AndroidやiOSのアプリ開発でいつも私がやっていることをFlutterでやってみた / Flutter development as usual

GDG神戸 Flutter勉強会(9/29)での登壇資料です
https://gdgkobe.doorkeeper.jp/events/78737

## キーワード
- クリーンアーキテクチャ
- 設定の開発と本番の切り替え
- CI/CD

## ソースコード
https://github.com/kwmt/flutter-inconne

## 参考
- Clean architecture https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
- DIについてわかりやすいと思う記事 https://blog.ishkawa.org/2017/09/18/1505701774/
- flavorについて https://medium.com/@salvatoregiordanoo/flavoring-flutter-392aaa875f36

Ce6acc3536b0e0340b5f0569d3394c9c?s=128

Yasutaka Kawamoto

September 29, 2018
Tweet

Transcript

  1. Android΍iOSͷΞϓϦ։ൃͰ ͍ͭ΋ࢲ͕΍͍ͬͯΔ͜ͱΛ FlutterͰ΍ͬͯΈͨ 2018/09/29 GDGਆށ Flutterษڧձ

  2. • Տຊ ହ޹(͔Θ΋ͱ ΍͔ͨ͢) • ॴଐɿגࣜձࣾ tech vein 
 (େࡕࢢதԝ۠ຊொ)

    • ϞόΠϧΞϓϦΤϯδχΞ • GitHub: kwmt ɺtwitter: kwmt27 • Google I/O2018 ॳࢀՃ 2 ࣗݾ঺հ
  3. 3 ΞϓϦ։ൃͰ͍ͭ΋΍͍ͬͯΔ͜ͱ 1. ઃܭ͸Clean Architecture 2. ։ൃͱຊ൪Ͱઃఆ੾ସ 3. CI/CD

  4. 1. Clean Architecture͸FlutterͰ࣮ݱͰ͖Δ͔ʁ

  5. 5 Clean Architecture https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

  6. • ϑϨʔϜϫʔΫͱಠཱ • ςετ͠΍͍͢ • UIͱಠཱ • σʔλϕʔεͱಠཱ • جຊతʹͲΜͳιϑτ΢ΣΞ(ΞϓϦͰ΋αʔόʔͰ΋)Ͱ

    ΋࠾༻Ͱ͖ΔͷͰɺͲ͜ʹԿ͕͋Δ͔͕౷Ұग़དྷͯ෼͔ Γ΍͍͢ Clean ArchitectureͷϝϦοτ 6
  7. Clean Architecture Screen

  8. Clean Architecture Screen

  9. Clean Architecture Use Case Screen

  10. Clean Architecture Use Case Repository Screen

  11. Clean Architecture Use Case Repository Screen Data Source

  12. Clean Architecture Use Case Model Repository Screen Data Source Entity

    ੺ɿґଘؔ܎ ੨ɿσʔλͷྲྀΕ
  13. Clean Architecture Use Case Model Repository Screen Data Source Entity

    ੺ɿґଘؔ܎ ੨ɿσʔλͷྲྀΕ
  14. Clean Architecture Presentation Use Case Model Repository Screen Data Source

    Entity ੺ɿґଘؔ܎ ੨ɿσʔλͷྲྀΕ Domain Infrastructure
  15. Clean Architecture Presentation Use Case Model Repository Screen Data Source

    Entity ੺ɿґଘؔ܎ ੨ɿσʔλͷྲྀΕ Domain Infrastructure
  16. • ΠϯλʔϑΣΠε͕͋Δ͜ͱ • DI͕Ͱ͖Δ͜ͱ Clean ArchitectureΛ࣮ݱ͢ΔͨΊʹ͸ 8

  17. • ͋Δ • interfaceͱ͍͏ΩʔϫʔυͷΠϯλʔϑΣΠε͸ͳ͍ • classΛఆٛ͢Δͱಉ໊ͷΠϯλʔϑΣΠε͕҉໧తʹ ఆٛ͞ΕΔ • ந৅ϝιουͷΈΛ΋ͭjavaͷΑ͏ͳΠϯλʔϑΣΠε ͷΑ͏ʹ͢Δʹ͸ɺabstractΩʔϫʔυΛ࢖ͬͯந৅

    ΫϥεΛ࡞Δ DartʹΠϯλʔϑΣΠε͕͋Δ͔ʁ abstract class Person { void walk(); } 9
  18. • DIͱ͸ɺඞཁͳ΋ͷΛ֎͔Β౉͢͜ͱɻ • ґଘؔ܎Λ؆୯ʹղܾͯ͘͠ΕΔDIϓϥάΠϯ͸͋Δ ͔ʁ • flutter_simple_dependency_injection DIͰ͖Δ͔ʁ https://pub.dartlang.org/packages/flutter_simple_dependency_injection 10

  19. 11 Clean Architecture - Domain - Model Use Case Model

    Repository Screen Data Source Entity ੺ɿґଘؔ܎ ੨ɿσʔλͷྲྀΕ Presentation Domain Infrastructure
  20. 11 Clean Architecture - Domain - Model Use Case Model

    Repository Screen Data Source Entity ੺ɿґଘؔ܎ ੨ɿσʔλͷྲྀΕ class Room { String id; String name; String photoUrl; } class User { String id; String name; String photoUrl; } Presentation Domain Infrastructure
  21. Presentation Domain Infrastructure 12 Clean Architecture - Domain - Use

    Case Use Case Model Repository Screen Data Source Entity ੺ɿґଘؔ܎ ੨ɿσʔλͷྲྀΕ
  22. Presentation Domain Infrastructure 12 Clean Architecture - Domain - Use

    Case Use Case Model Repository Screen Data Source Entity ੺ɿґଘؔ܎ ੨ɿσʔλͷྲྀΕ /// ϢʔβʔͷRoomϦετΛऔಘ͢Δ abstract class FetchRoomListUseCase { Future<List<Room>> execute(User user); }
  23. Presentation Domain Infrastructure 12 Clean Architecture - Domain - Use

    Case Use Case Model Repository Screen Data Source Entity ੺ɿґଘؔ܎ ੨ɿσʔλͷྲྀΕ /// ϢʔβʔͷRoomϦετΛऔಘ͢Δ abstract class FetchRoomListUseCase { Future<List<Room>> execute(User user); } class FetchRoomListUseCaseImpl implements FetchRoomListUseCase { final RoomRepository _roomRepository; FetchRoomListUseCaseImpl(this._roomRepository); @override Future<List<Room>> execute(User user) { return _roomRepository.fetchRoomList(user); } }
  24. 13 Clean Architecture - Domain - Repository Use Case Model

    Repository Screen Data Source Entity ੺ɿґଘؔ܎ ੨ɿσʔλͷྲྀΕ abstract class RoomRepository { Future<List<Room>> fetchRoomList(User user); } Presentation Domain Infrastructure
  25. 14 Clean Architecture - Infrastructure - Data Source Use Case

    Model Repository Screen Data Source Entity ੺ɿґଘؔ܎ ੨ɿσʔλͷྲྀΕ Presentation Domain Infrastructure
  26. 14 Clean Architecture - Infrastructure - Data Source Use Case

    Model Repository Screen Data Source Entity ੺ɿґଘؔ܎ ੨ɿσʔλͷྲྀΕ class FirestoreDatasource implements RoomRepository { final Firestore _firestore; FirestoreDatasource(this._firestore); @override Future<List<Room>> fetchRoomList(User user) async { QuerySnapshot querySnapshot = await _firestore .collection('rooms') .getDocuments(); return _fetchRoomListWithMembers(querySnapshot); } } Presentation Domain Infrastructure
  27. DIͰͭͳ͙ class FirestoreDatasource implements RoomRepository { final Firestore _firestore; FirestoreDatasource(this._firestore);

    } 15
  28. DIͰͭͳ͙ class DependencyInjection { void init() { // DIϓϥάΠϯ͔ΒinjectorΛऔಘ final

    injector = Injector.getInjector(); // औಘͨ͠injectorʹରͯ͠FirestoreΠϯελϯεΛੜ੒͠ηοτ͢Δ injector.map<Firestore>((i) => Firestore(), isSingleton: true); // FirestoreDatasourceͷίϯετϥΫλͷҾ਺ʹ // FirestoreΠϯελϯεΛ౉͢ injector.map<FirestoreDatasource>( (i) => FirestoreDatasource(i.get<Firestore>()), isSingleton: true); } } class FirestoreDatasource implements RoomRepository { final Firestore _firestore; FirestoreDatasource(this._firestore); } 15
  29. DIͰͭͳ͙ class DependencyInjection { void init() { // লུ injector.map<RoomRepository>((i)

    => i.get<FirestoreDatasource>(), isSingleton: true); injector.map<FetchRoomListUseCase>((i) => FetchRoomListUseCaseImpl(i.get<RoomRepository>()), isSingleton: true); } } 16
  30. DIͰͭͳ͙ class DependencyInjection { void init() { // লུ injector.mapWithParams<RoomListScreen>((i,

    p) => RoomListScreen( p["user"], i.get<FetchRoomListUseCase>() )); } } 17
  31. RoomListScreenͰUseCaseΛ࢖͏ class RoomListScreen extends StatefulWidget final User _user; final FetchRoomListUseCase

    _fetchRoomListUseCase; RoomListScreen( this._user, this._fetchRoomListUseCase, ) @override _RoomListScreen createState() => _RoomListScreen(); } 18
  32. RoomListScreenͰUseCaseΛ࢖͏ class _RoomListScreen extends State<RoomListScreen> { List<Room> _rooms = List();

    bool _isProgress = false; @override void initState() { super.initState(); _isProgress = true; widget._fetchRoomListUseCase.execute(widget._user).then((rooms) { setState(() { this._rooms = rooms; _isProgress = false; }); }); } 19
  33. 2. ։ൃͱຊ൪ͷઃఆΛ੾Γସ͑Δʹ͸ʁ

  34. ։ൃɾຊ൪؀ڥΛ੾Γସ͑Δʹ͸ • Android΍iOSͷ͍ͭ΋ͷઃఆ͕ඞཁ • Android:flavorͷઃఆͳͲ • iOS: Schemeઃఆ΍։ൃ༻ͷinfo.plist࡞੒ͳͲ • ։ൃ͸development,

    ຊ൪͸productionͱ໊લΛ෇͚ͨ ͱ͠·͢ɻ 21
  35. InheritedWidget class https://docs.flutter.io/flutter/widgets/InheritedWidget-class.html • WidgetπϦʔͷԼҐπϦʔͷWidgetʹޮ཰తʹ఻͑Δ ϕʔεΫϥε • context͔ΒInheritedWidgetͷΠϯελϯεΛऔಘ͢Δʹ ͸ɺBuildContext.inheritFromWidgetOfExactTypeΛ࢖͏ •

    BuildContext.inheritFromWidgetOfExactTypeΛ࢖ͬͯࢀ র͞ΕͨInheritedWidget͸ɺWidgetͷঢ়ଶ͕มΘͬͨͱ ͖ɺ࠶ߏஙͤ͞Δ͜ͱ͕Ͱ͖Δ 22
  36. InheritedWidget class Λ࢖ͬͨྫ APP Root Login Home auth auth auth

    23
  37. InheritedWidget class Λ࢖ͬͨྫ APP Root Login Home auth auth auth

    Root(auth:Auth()) ະϩάΠϯͳΒ Login(auth: auth) ϩάΠϯࡁΈͳΒ Home(auth: auth) 23
  38. InheritedWidget class Λ࢖ͬͨྫ 24 APP Login Root Widget A Widget

    B Home auth auth auth auth auth
  39. InheritedWidget class Λ࢖ͬͨྫ WidgetB(auth: auth) Login(auth: auth) WidgetA(auth: auth) 24

    APP Login Root Widget A Widget B Home auth auth auth auth auth
  40. InheritedWidget class Λ࢖ͬͨྫ class AuthProvider extends InheritedWidget { final Auth

    auth; AuthProvider({this.auth, Widget child}) : super(child: child); static AuthProvider of(BuildContext context) { return context.inheritFromWidgetOfExactType(AuthProvider); } @override bool updateShouldNotify(AuthProvider oldWidget) => true; } 25
  41. InheritedWidget class Λ࢖ͬͨྫ APP Auth Provider Root Login Home auth

    InheritedWidgetΛܧঝ 26
  42. InheritedWidget class Λ࢖ͬͨྫ APP Auth Provider Root Login Home auth

    InheritedWidgetΛܧঝ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return AuthProvider( auth: Auth(), child: App(); } } 26
  43. InheritedWidget class Λ࢖ͬͨྫ APP Auth Provider Root Login Home auth

    AuthProvider.of(context).auth AuthProvider.of(context).auth AuthProvider.of(context).auth InheritedWidgetΛܧঝ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return AuthProvider( auth: Auth(), child: App(); } } 26
  44. AppConfig class AppConfig extends InheritedWidget { AppConfig({this.baseWebUrl, Widget child}) :

    super(child: child); static AppConfig of(BuildContext context) { return context.inheritFromWidgetOfExactType(AppConfig); } @override bool updateShouldNotify(AppConfig oldWidget) => false; final String baseWebUrl; String get privacyPolicyUrl => this.baseWebUrl + "/privacy.html"; } 27
  45. mainϑΝΠϧΛ։ൃɾຊ൪ʹ෼͚Δ // main.dart void main() { runApp(MyApp()); } 28

  46. mainϑΝΠϧΛ։ൃɾຊ൪ʹ෼͚Δ // main.dart void main() { runApp(MyApp()); } // main_development.dart

    void main() { runApp(MyApp()); } 28
  47. mainϑΝΠϧΛ։ൃɾຊ൪ʹ෼͚Δ // main.dart void main() { runApp(MyApp()); } // main_development.dart

    void main() { runApp(MyApp()); } // main_production.dart void main() { runApp(MyApp()); } 28
  48. mainϑΝΠϧΛ։ൃɾຊ൪ʹ෼͚Δ // main_development.dart void main() { var appConfig = AppConfig(

    baseWebUrl: “https://development.com”, child: MyApp() ); runApp(appConfig); } 29
  49. mainϑΝΠϧΛ։ൃɾຊ൪ʹ෼͚Δ // main_development.dart void main() { var appConfig = AppConfig(

    baseWebUrl: “https://development.com”, child: MyApp() ); runApp(appConfig); } // main_production.dart void main() { var appConfig = AppConfig( baseWebUrl: “https://production.com”, child: MyApp() ); runApp(appConfig); } 29
  50. AppConfigΛ࢖͏ AppConfig.of(context).baseWebUrl 30

  51. ࣮ߦ࣌΍Ϗϧυ࣌ʹ։ൃ͔ຊ൪Λࢦఆ͢Δʹ͸ $ flutter run -t lib/main_development.dart --flavor development 31

  52. Android StudioͰ੾Γସ͑Δʹ͸ 32

  53. Android StudioͰ੾Γସ͑Δʹ͸ 33

  54. Android StudioͰ੾Γସ͑Δʹ͸ 34

  55. Android StudioͰ੾Γସ͑Δʹ͸ 34

  56. 3. CI/CD͸Ͳ͏͢Δͷ͕ྑͦ͞͏͔ʁ

  57. 36 CI/CD CI αʔϏε Git αʔϏε

  58. 36 CI/CD CI αʔϏε Git αʔϏε

  59. 36 CI/CD CI αʔϏε Git αʔϏε

  60. 36 CI/CD CI αʔϏε Git αʔϏε

  61. 36 CI/CD CI αʔϏε Git αʔϏε

  62. GitαʔϏε Public Private GitHub ແྉ ༗ྉ GitLab ແྉ ແྉ 37

  63. GitαʔϏε Public Private GitHub ແྉ ༗ྉ GitLab ແྉ ແྉ 37

  64. CIαʔϏε Android iOS උߟ circlecI ແྉ ༗ྉ Bitrise ແྉ ແྉ

    10෼/1Ϗϧυ×200ճ/݄ Cirrus CI ແྉ ແྉ publicϦϙδτϦͷΈ privateϦϙδτϦ͸༗ྉ 38
  65. CIαʔϏε Android iOS උߟ circlecI ແྉ ༗ྉ Bitrise ແྉ ແྉ

    10෼/1Ϗϧυ×200ճ/݄ Cirrus CI ແྉ ແྉ publicϦϙδτϦͷΈ privateϦϙδτϦ͸༗ྉ 38
  66. CIαʔϏεxGitαʔϏε GitHub GitLab circleci ରԠ ඇରԠ Bitrise ରԠ ରԠ Cirrus

    CI ରԠ ඇରԠ 39
  67. CIαʔϏεxGitαʔϏε GitHub GitLab circleci ରԠ ඇରԠ Bitrise ରԠ ରԠ Cirrus

    CI ରԠ ඇରԠ 39
  68. 40 ࠓճͷΞϓϦ։ൃͰͷ࢖༻αʔϏε CI αʔϏε Git αʔϏε

  69. 40 ࠓճͷΞϓϦ։ൃͰͷ࢖༻αʔϏε CI αʔϏε

  70. 40 ࠓճͷΞϓϦ։ൃͰͷ࢖༻αʔϏε

  71. 41 Bitrise 10෼/1Ϗϧυ(ແྉϓϥϯ)

  72. 42

  73. 42

  74. 43 Fastlane https://flutter.io/fastlane-cd/

  75. 44 Bitrise+DeployGate https://qiita.com/kyoro353/items/200d5b34b9f5805dd43a

  76. 45 ·ͱΊ • ΫϦʔϯΞʔΩςΫνϟͰઃܭ͸Ͱ͖Δ • ։ൃͱຊ൪Ͱઃఆͷ੾ସՄೳ • CI/CD͸࢖͏αʔϏεͷ૊Έ߹ΘͤʹΑͬͯ͸ແྉͰ࣮ ݱͰ͖ͦ͏Ͱ͕͢ɺprivateϦϙδτϦͷ৔߹͸ݱঢ়ແ ྉͰͷ࣮ݱ͸ݫ͍͠

  77. Happy Fluttering!