1. Switch Bundle ID and app name between production and development versions
We want to have a production and a development version of the app living together on one device. Also, I want to use the development version for debug builds and the production version for release builds. iOS and Android.
iOS.
Launch IOS/Runner.xcworkspace in xcode
ios/Flutter/Debug.xcconfig
#include "Generated.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER = nrikiji.flutter-start-app.dev
DISPLAY_NAME = Debug StartApp
ios/Flutter/Release.xcconfig
#include "Generated.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER = nrikiji.flutter-start-app
DISPLAY_NAME = StartApp
Apply the app name in xcconfig
ios/Runner/Info.plist
<?xml version="1.0" encoding="UTF-8"? >
...
<dict> ...
...
<key>CFBundleDisplayName</key
<string>$(DISPLAY_NAME)</string>
Delete the Runner column of Product Bundle Identifier in TARGETS > Runner > Build Settings (delete button) to reflect the Bundle ID in xcconfig.
Android
Preparing signings for production
.gitignore
# Android Signing
android/app/signing/key.jks
android/app/signing/signing.gradle
Add the JKS file to the project
android/app/signing/key.jks
android/app/signing/signing.gradle
signingConfigs {
release {
storeFile file("key.jks")
storePassword "xxxxx"
keyAlias "xxxxx"
keyPassword "xxxxx"
}
}
Set Bundle ID, app name and signing for production build
android/app/build.gradle
android {
・・・
defaultConfig {
・・・
applicationId "nrikiji.flutter_start_app"
・・・
}
buildTypes {
debug {
debuggable true
applicationIdSuffix ".dev"
resValue "string", "app_name", "Debug StartApp"
}
release {
debuggable false
applicationIdSuffix ""
resValue "string", "app_name", "StartApp"
apply from: './signing/signing.gradle', to: android
signingConfig signingConfigs.release
}
}
Make sure to use the app name you set above.
android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="nrikiji.flutter_start_app">
<application
android:label="@string/app_name"
・・・
2. Switch firebase configuration file between production and development versions
I want to use firebase analytics and crashlytics as a minimum in any app. I also assume that you have already created a firebase project and app. iOS, Android and flutter.
iOS
Download GoogleService-Info.plist from firebase console and place it under IOS/Runner. Use different file names for development and production.
$ ls -l
ios/Runner/GoogleService-Info-dev.plist
ios/Runner/GoogleService-Info-prod.plist
.gitignore
# Firebase Settings Files
ios/Runner/GoogleService-Info-dev.plist
ios/Runner/GoogleService-Info-prod.plist
Configure Run Script in TARGETS > Runner > Build Phases to develop GoogleService-Info.plist and switch it in production build.
script
if [ "${CONFIGURATION}" == "Debug" ]; then
cp -r "${PROJECT_DIR}/Runner/GoogleService-Info-dev.plist" "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.app/GoogleService-Info.plist"
elif [ "${CONFIGURATION}" == "Release" ]; then
cp -r "${PROJECT_DIR}/Runner/GoogleService-Info-prod.plist" "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.app/GoogleService-Info.plist"
fi
Android
Download google-services.json from firebase console and place it under debug and release (create directories) under android/app/src.
$ ls -l
android/app/src/debug/google-services.json
android/app/src/release/google-services.json
.gitignore
# Firebase Settings Files
android/app/src/debug/google-services.json
android/app/src/release/google-services.json
Add the gradle plugin
android/build.gradle
buildscript {
・・・
dependencies {
classpath 'com.google.gms:google-services:4.3.4'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.1'
・・・
}
}
android/app/build.gradle
・・・
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
Initializing firebase in flutter
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
void main() {
runZonedGuarded(
() {
WidgetsFlutterBinding.ensureInitialized();
return runApp(ProviderScope(child: MyApp()));
},
(e, st) {
FirebaseCrashlytics.instance.recordError(e, st);
},
);
}
class MyApp extends StatelessWidget {
final _initialization = Firebase.initializeApp();
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _initialization,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done || snapshot.hasError) {
return MaterialApp(・・・);
} else {
return SizedBox.shrink();
}
});
}
}
3. Switch the constants used in the program between the production and development versions.
I want to use different URLs for Web API connections in the production and development versions. Also, I want to use development constants for debug builds and production constants for release builds.
AndroidStudio Build Settings
Add debug and release from "Edit Configurations" in Android Studio.
debug: set "--dart-define env=dev" to "Addional arguments"
release: set "--release --dart-define env=prod" to "Addional arguments"
When building from the command line
# Debug
$ flutter build ios --dart-define=env=dev
# Release(iOS)
$ flutter build ios --release --dart-define=env=prod
# Release(Android)
$ flutter build appbundle --release --dart-define=env=prod --no-shrink
Constant definitions for each environment
class Environment {
final EnvironmentKind kind;
final String baseApiUrl;
factory Environment() {
const env = String.fromEnvironment('env');
return env == 'prod' ? Environment.prod() : Environment.dev();
}
const Environment._({
this.kind,
this.baseApiUrl,
});
const Environment.prod()
: this._(
kind: EnvironmentKind.Prod,
baseApiUrl: "https://example.com",
);
const Environment.dev()
: this._(
kind: EnvironmentKind.Dev,
baseApiUrl: "https://dev.example.com",
);
}
enum EnvironmentKind {
Dev,
Prod,
}
4. Localization
pubspec.yaml
dependencies:
・・・
flutter_localizations:
sdk: flutter
lib/localize.dart
import 'package:flutter/cupertino.dart';
class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
const AppLocalizationsDelegate();
@override
bool isSupported(Locale locale) => ['en', 'ja'].contains(locale.languageCode);
@override
Future<AppLocalizations> load(Locale locale) async => AppLocalizations(locale);
@override
bool shouldReload(AppLocalizationsDelegate old) => false;
}
class AppLocalizations {
final LocalizeMessages messages;
AppLocalizations(Locale locale) : this.messages = LocalizeMessages.of(locale);
static LocalizeMessages of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations)!.messages;
}
}
class LocalizeMessages {
final String greet;
LocalizeMessages({
required this.greet,
});
factory LocalizeMessages.of(Locale locale) {
switch (locale.languageCode) {
case 'ja':
return LocalizeMessages.ja();
case 'en':
return LocalizeMessages.en();
default:
return LocalizeMessages.en();
}
}
factory LocalizeMessages.ja() => LocalizeMessages(
greet: 'こんにちは',
);
factory LocalizeMessages.en() => LocalizeMessages(
greet: 'Hello',
);
}
5. Binary upload to store with GitHub Actions
Once I learned to prevent build errors, I became lazy to upload manually. Also, the App Store Connect API and Google Play Developer Publishing API should be enabled.
Prepare the following file and set the necessary values in the GitHub Actions secrets so that the workflow will be executed when the git tag is pushed.
workflow
.github/workflows/release.yml
fastlane
Gemfile
ios/fastlane/Appfile
ios/fastlane/Fastfile
GitHub Actions secrets
this url
Summary
This is a startup project that you can start by git cloning what I wrote here. The usage procedure is summarized in the README.
Top comments (0)