Slide 1

Slide 1 text

Flavor Of Flutter Flutter Meetup Tokyo #1 FlutterͰstaging/production؀ڥͷ੾ସ

Slide 2

Slide 2 text

ࣗݾ঺հ • @fullfool / FURUKI Eiji • iOS / Android ΤϯδχΞ • ϞΠגࣜձࣾ • Flutterྺɹࡢ೥8݄த०͔Β • ࠷ۙFlutter৮ͬͯͳ͍

Slide 3

Slide 3 text

agenda

Slide 4

Slide 4 text

agenda • Flavorͱ͸ • DartͰFlavorΛऔಘ͢Δ • AndroidStudio, XcodeͰFlavorΛࢦఆ͢Δ • PlatformຖͷFlavorͷఆٛ • Firebase, FastlaneͱFlavor

Slide 5

Slide 5 text

agenda • Flavorͱ͸ • DartͰFlavorΛऔಘ͢Δ • AndroidStudio, XcodeͰFlavorΛࢦఆ͢Δ • PlatformຖͷFlavorͷఆٛ • Firebase, FastlaneͱFlavor

Slide 6

Slide 6 text

Flavorͱ͸ʁ

Slide 7

Slide 7 text

Flavorͱ͸ʁ • ҰͭͷϓϩδΣΫτͰdemo൛ʗfull൛ɺࣾ಺ ςετ༻ʗετΞ༻ͳͲͷෳ਺ͷ؀ڥΛఆٛ ͢Δ࢓૊Έ • AndroidͰ͸ ProductFlavor • iOSͰ͸ Scheme

Slide 8

Slide 8 text

FlavorΛࢦఆͯ͠ΞϓϦΛ࣮ߦ $ flutter run --flavor staging $ flutter run --flavor production `--flavor` ΦϓγϣϯͰFlavorΛࢦఆ͢Δ flavorʹࢦఆ͢Δ஋͸Platformଆ(Android/iOS)ͰઃఆࡁΈͷ஋͕ࢦఆͰ ͖·͢
 ͦΕͧΕͷPlatformͰͷFlavorͷఆٛ͸εϥΠυ຤ඌʹ

Slide 9

Slide 9 text

DartͰFlavorΛऔಘ͢Δํ๏

Slide 10

Slide 10 text

DartͰFlavorΛऔಘ void main() { const String flavor = const String.fromEnvironment('flavor'); configure(flavor); runApp(new MyApp()); } const bool isReleaseMode = const bool.fromEnvironment('dart.vm.product'); ىಈ࣌ʹFlavorΛऔಘ͍ͨ͠ releaseϞʔυΛऔಘ͢Δ࣌

Slide 11

Slide 11 text

DartͰFlavorΛऔಘ void main() { const String flavor = const String.fromEnvironment('flavor'); configure(flavor); runApp(new MyApp()); } const bool isReleaseMode = const bool.fromEnvironment('dart.vm.product'); ىಈ࣌ʹFlavorΛऔಘ͍ͨ͠ releaseϞʔυΛऔಘ͢Δ࣌

Slide 12

Slide 12 text

DartͰFlavorຖͷॲཧΛߦ͏ ̎ͭͷํ๏ • Flavor औಘ༻ͷ MethodChannel Λ࡞Δ • Flavor ຖʹ Dart Entry-Point Λมߋ͢Δ

Slide 13

Slide 13 text

MethodChannelͱ͸ • Platform(Obj-C/Java)Ͱఆٛͨؔ͠਺Λඇಉظ ݺͼग़͢͠ΔͨΊͷΫϥε • ىಈ࣌ʹPlatformଆͰMethodChannelΛొ࿥ • DartଆͰMethodChannelΛ࢖༻͢Δ͜ͱͰɺ PlatformͰͷॲཧ݁ՌΛऔಘͰ͖Δ

Slide 14

Slide 14 text

MethodChannelͷొ࿥(Android) new MethodChannel(getFlutterView(), "flavor") .setMethodCallHandler(new MethodChannel.MethodCallHandler() { @Override public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { result.success(BuildConfig.FLAVOR); } }); ΞϓϦىಈ࣌ʹMethodChannelΛొ࿥͢Δ ʢ͜ͷchannel͸Flavorऔಘઐ༻ͷͨΊMethodCall͸ࢀর͠ͳ͍ʣ

Slide 15

Slide 15 text

MethodChannelͷొ࿥(Android) new MethodChannel(getFlutterView(), "flavor") .setMethodCallHandler(new MethodChannel.MethodCallHandler() { @Override public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { result.success(BuildConfig.FLAVOR); } }); ProductFlavor(BuildConfig.FLAVOR)Λฦ͢

Slide 16

Slide 16 text

MethodChannelͷొ࿥(iOS) FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController; FlutterMethodChannel* flavorChannel = [FlutterMethodChannel methodChannelWithName:@"flavor" binaryMessenger:controller]; [flavorChannel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) { NSString* flavor = (NSString*)[[NSBundle mainBundle] .infoDictionary valueForKey:@"Flavor"]; result(flavor); }]; Bundleʹఆٛ͢ΔSchemeʹؔ࿈෇͚ͨFlavorΛฦ͢

Slide 17

Slide 17 text

MethodChannelͰFlavorऔಘ void main() { _run(); } Future _run() async { var flavor = await MethodChannel('flavor').invokeMethod('getFlavor'); configure(flavor); runApp(new MyApp()); } main.dartͰFlavorΛऔಘͯ͠ɺઃఆΛॳظԽ

Slide 18

Slide 18 text

Dart Entry-Pointͱ͸ • ೚ҙͷdartϑΝΠϧΛࢦఆ͢Δ • ࢦఆͨ͠dartϑΝΠϧͷmain()ؔ਺͔Β࢝·Δ $ flutter run --flavor staging -t lib/main_staging.dart

Slide 19

Slide 19 text

FlavorຖͷEntry-Point • main.dart͸ "production"ͰઃఆॳظԽ void main() { configure("staging"); runApp(new MyApp()); } void main() { configure("production"); runApp(new MyApp()); } • main_staging.dart͸ "staging"ͰઃఆॳظԽ

Slide 20

Slide 20 text

FlavorΛѻ͏৔߹ MethodCallͱEntry-Point Ͳͬͪ࢖͑͹ʁ

Slide 21

Slide 21 text

MethodCallͱEntry-Point • MethodChannel • Obj-C, Java(Swift, Kotlin)΋༻ҙ͢Δ • Pluginͱಉ͡ͳͷͰɺ׳Ε͓ͯ͘ͱPlugin࡞Γ΍͍͢ • Entry-Point • Dart͚ͩͰ׬݁͢ΔͷͰ͓खܰ • XcodeͰͷ؀ڥ੾ସϛε͋Γͦ͏

Slide 22

Slide 22 text

࣮ߦ࣌ͷFlavorࢦఆ

Slide 23

Slide 23 text

࣮ߦ࣌ͷFlavorࢦఆ(CLI) • CLIͰ࣮ߦ͢Δ৔߹͸ɺ͜ͷίϚϯυ • AndroidStudio, XcodeͰ࣮ߦ͢Δ࣌ʹFlavor(Entry-Point)Λࢦఆ͢ Δʹ͸ʁ $ flutter run --flavor staging -t lib/main_staging.dart

Slide 24

Slide 24 text

࣮ߦ࣌ͷFlavorࢦఆ (AndroidStudio Flutter Plugin) Menu > Run > Edit Configurations... ʹͯ "Build flavor"ͱ "Dart entrypoint" Λࢦఆ͢Δ

Slide 25

Slide 25 text

࣮ߦ࣌ͷFlavorࢦఆ(Xcode) • Xcode͔ΒFlutterΞϓϦΛ࣮ߦ͢Δ৔߹ʹ͸ `build` ίϚϯυͰม ߋͰ͖Δ • ίϚϯυ࣮ߦޙɺXcodeͰSchemeΛมߋ͢Δ $ flutter build ios --flavor staging -t lib/main_staging.dart

Slide 26

Slide 26 text

·ͱΊ

Slide 27

Slide 27 text

·ͱΊ • DartͰFlavorΛѻ͏ʹ͸ɺ̎ͭͷํ๏͕͋Δ • XcodeͰ௚઀࣮ߦ͢Δ࣌΍ɺFastlane΍CIͰ͸ `flutter build` Ͱ؀ڥΛ੾ସ͑Δ͜ͱʹཹҙ͢ ΔɻͦΕҎ֎͸ࠓ·ͰͦΕͧΕͷPlatformͰ ΍͍ͬͯͨํ๏Ͱɻ

Slide 28

Slide 28 text

PlatformͰͷFlavorఆٛ

Slide 29

Slide 29 text

PlatformͰͷFlavorఆٛ(Android) android { ... defaultConfig {...} buildTypes {...} flavorDimensions "api" productFlavors { staging { dimension "api" applicationIdSuffix ".staging" manifestPlaceholders = [appName: "Casmo dev"] } production { dimension "api" manifestPlaceholders = [appName: "Casmo"] } } } ௨ৗͷAndroidΞϓϦͷProductFlavorsͱಉ༷

Slide 30

Slide 30 text

PlatformͰͷFlavorఆٛ(iOS) • SchemeΛઃఆ • ConfigurationΛઃఆ • Build SettingsͷPRODUCT_BUNDLE_IDENTIFIERΛมߋ • Build SettingsͷUser-Defined ValueΛ௥Ճ • info.plistʹFlavorͷkey-valueΛ௥Ճ

Slide 31

Slide 31 text

PlatformͰͷFlavorఆٛ(iOS) • SchemeΛઃఆ

Slide 32

Slide 32 text

PlatformͰͷFlavorఆٛ(iOS) • ConfigurationΛઃఆ

Slide 33

Slide 33 text

PlatformͰͷFlavorఆٛ(iOS) • Build SettingsͷPRODUCT_BUNDLE_IDENTIFIERΛมߋ

Slide 34

Slide 34 text

PlatformͰͷFlavorఆٛ(iOS) • Build SettingsͷUser-Defined ValueΛ௥Ճ ($PRODUCT_FLAVOR)

Slide 35

Slide 35 text

PlatformͰͷFlavorఆٛ(iOS) • info.plistʹFlavorͷkey-value ($Flavor ) Λ௥Ճ

Slide 36

Slide 36 text

Firebaseͷ੾ସ

Slide 37

Slide 37 text

Firebaseͷ੾ସ(Android) src/$flavor ຖʹ google-services.json Λ഑ஔ

Slide 38

Slide 38 text

Firebaseͷ੾ସ(iOS) GoogleService-Info-$flavor.plist Λىಈ࣌ʹಡ Έସ͑Δ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSString *flavor = (NSString*)[[NSBundle mainBundle] .infoDictionary valueForKey:@"Flavor"]; NSString *firebaseInfo = [NSString stringWithFormat: @"GoogleService-Info-%@", flavor]; NSString *path = [[NSBundle mainBundle] pathForResource:firebaseInfo ofType:@"plist"]; FIROptions* firOptions = [[FIROptions alloc] initWithContentsOfFile:path]; if (firOptions == nil) { assert("Couldn't load config file"); } [FIRApp configureWithOptions:firOptions];

Slide 39

Slide 39 text

Fastlane+Beta

Slide 40

Slide 40 text

Fastlane+Beta(Android) lane :beta do |options| flavor = (options[:prod] ? "production" : "staging") entry_dart = (options[:prod] ? "lib/main.dart" : "lib/main_staging.dart") sh("cd ../.. && flutter build apk --flavor #{flavor} -t #{entry_dart} --release && cd -") changelog_from_git_commits # upload to Beta crashlytics( ... ) slack( ... ) end flutter buildͰ؀ڥΛ੾Γସ͑ͯϏϧυ͢Δ

Slide 41

Slide 41 text

Fastlane+Beta(iOS) lane :beta do |options| scheme = (options[:prod] ? "Production" : "Staging") flavor = (options[:prod] ? "production" : "staging") entry_dart = (options[:prod] ? "lib/main.dart" : "lib/main_staging.dart") sh("cd ../.. && flutter build ios --flavor #{flavor} -t #{entry_dart} --release && cd -") gym( scheme: scheme, configuration: "Release #{scheme}", export_method: "ad-hoc" ) changelog_from_git_commits # upload to Beta crashlytics( ... ) slack( ... ) end flutter buildͰ؀ڥΛ੾Γସ͑ͯϏϧυ͢Δ