Release A Flutter-built Android App For Testing In Google Play In 32 Simple Steps
As of: 11/26/2023
These are the steps I took to release my aforementioned Flutter-built app to an internal test track in the Google Play Store. Where I thought it would be helpful for others, I've included notes identifying obstacles I had to get around in order to proceed. If you attempt to follow these steps and something has changed since I published this post, please do reach out (via the link at the bottom of the page) and let me know what you had to do differently, so I can update my instructions accordingly.
I used Android Studio for my Flutter development but you can use Visual Studio Code, which also has Flutter/Dart extensions.
OK, here goes!
Created a Google Play Account:
-
Opened browser, navigated to: https://play.google.com/console/signup
NOTE: If you are signed in to a Google account already, it will be assumed you want to use that for your Play Account. If you want to use/create a different account or if you're not signed in to a Google account, follow the relevant path Google will take you through. -
Chose type: An organization
- Entered Developer name
- Clicked Create a payments profile and entered D-U-N-S number for my organization
- Selected Organization Type: A company or business
- Selected Organization Size: 1-10
- Entered Organization Phone Number
- Entered Organization Website
- Entered a contact phone number and email address for Google Play users
- Entered a contact phone number and email address for Google
- Paid $25 (one-time) registration fee
NOTE: Because I created an organization type account, I was then prompted to provide business verification documentation. I used the following, though there are various options for each type of document Google requires. - Uploaded EIN notice from IRS
- Uploaded photos of front and back of my driver's license
- Provided mailing address
NOTE: Though it said I might have to wait a few days for them to complete my business verification, it only took about 12 hours. :)
Added a Launcher Icon:
-
Used https://imageresizer.com/ to resize the .png file, from vector set I purchased, to five different sizes, per Android standards (/android/app/src/main/res/):
- mipmap-hdpi: 72x72
- mipmap-mdpi: 48x48
- mipmap-xhdpi: 96x96
- mipmap-xxhdpi: 144x144
- mipmap-xxxhdpi: 192x192
- Copied each of the resized icons ^^^ into respective folder (e.g., "mipmap-hdpi", "mipmap-mdpi", etc.) and renamed each icon to "[MY APP NAME]_launch.png"
-
In AndroidManifest.xml, updated:
- the application tag’s android:label attribute to "[MY APP NAME]"
- the application tag’s android:icon attribute to "@mipmap/[MY APP NAME]_launcher"
-
In build.gradle, (per: https://developer.android.com/build/configure-app-module#change_the_package_name):
- Changed "defaultConfig { applicationId" to "com.[MY COMPANY NAME].[MY APP NAME WITH NO PUNCTUATION]" (to match what I used for iOS app, not that it REALLY matters, but I like consistency when I can have it. :) )
-
Left "android { namespace" as "com.[MY COMPANY NAME].[MY APP NAME WITH AN UNDERSCORE BETWEEN WORDS]"
NOTE: While it is tempting to change this to match the applicationId, because I created my Flutter project named "[MY APP NAME WITH AN UNDERSCORE BETWEEN WORDS]" the resultant Kotlin package name was created as that way also so I need to leave this as-is or things break without additional refactoring I didn't want to deal with.
- I confirmed that the default icon was successfully replaced with my custom one, by running the app and inspecting the app icon in the Launcher
NOTE: If your app uses Platform Views, you might want to enable Material Components by following the steps described in the Getting Started guide for Android (https://flutter-ko.dev/deployment/android#enabling-material-components
Signed the App:
NOTE: To publish on the Play Store, you need to sign your app with a digital certificate.
Android uses two signing keys: upload and app signing.
- Developers upload an .aab or .apk file signed with an upload key to the Play Store.
- The end-users download the .apk file signed with an app signing key.
- Created an upload keystore:
-
From project root, ran the following command:
$ flutter doctor -v
- Decided on a directory on local computer to save keystore ([KEYSTORE_DIR])
- Chose a strong password to use for keystore creation and saved it somewhere secure
- In the resultant output, under "Android toolchain...", found and saved path value following "Java binary at:"
-
Ran the following command, replacing [JAVA_BINARY_AT] with saved path value MINUS "/java" and [KEYSTORE_DIR] with literal value chosen above:
$ [JAVA_BINARY_AT]/keytool -genkey -v -keystore [KEYSTORE_DIR]/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload
- Entered Password
- Entered First and last name
- Entered Organizational Unit: Mobile
- Entered Organization: [my company name]
- Entered City: [my company's city name]
- Entered State: [my company's state name, spelled out]
- Entered Country Code: US
- Entered "yes" after reviewing my entered data
- Confirmed I could see keystore.jks file had been created in specified dir
-
From project root, ran the following command:
-
Referenced the keystore from the app:
Created a file named [project]/android/key.properties that contains a reference to the keystore, replacing values in [] with literal values:
storePassword=[password-from-previous-step] keyPassword=[password-from-previous-step] keyAlias=upload storeFile=[KEYSTORE_DIR]/upload-keystore.jks
-
Configured signing in Gradle:
-
Configured gradle to use the upload key when building app in release mode by editing the [project]/android/app/build.gradle file:
-
Added the keystore information from the properties file before the android block:
def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } android { ... }
-
Found the buildTypes block:
buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, // so `flutter run --release` works. signingConfig signingConfigs.debug } }
-
And replaced it with the following signing configuration info:
signingConfigs { release { keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null storePassword keystoreProperties['storePassword'] } } buildTypes { release { signingConfig signingConfigs.release } }
-
Added the keystore information from the properties file before the android block:
-
Configured gradle to use the upload key when building app in release mode by editing the [project]/android/app/build.gradle file:
-
Cleared out old build settings in order to pick up Gradle (and other) updates, by running the following command:
$ flutter clean
NOTE: If your app is large or uses large plugins, you might need to enable multidex support (https://flutter-ko.dev/deployment/android#enabling-multidex-support)
Reviewed the gradle.build configuration:
NOTE: Depending on how you've built your app, you may need/want to change some/all of these settings:
-
Confirmed the following settings defined under the defaultConfig block:
- applicationId: (already done in "Add a Launcher Icon" section above)
- minSdkVersion: (left as "flutter.minSdkVersion")
- targetSdkVersion: (left as "flutter.targetSdkVersion")
- versionCode: (left as "flutter.versionName" [from local.properties])
- versionName: (left as "flutter.versionCode" [from local.properties])
- buildToolsVersion: (left out as I didn't need this)
-
Confirmed the following settings defined under the android block:
compileSdkVersion: (left as "flutter.compileSdkVersion")
NOTE: If you change any of these you probably want to re-run:
$ flutter clean
Built the app for release:
-
Built an app bundle from within Flutter project directory:
$ cd [project]
$ flutter build appbundle --obfuscate --split-debug-info=v1.1.0+1
- Confirmed the release bundle (app-release.aab) was created in the project's "build/app/outputs/bundle/release" directory
Created And Set Up App in Play Console:
-
Created and set up app (per: https://support.google.com/googleplay/android-developer/answer/9859152):
- Opened Play Console
- Selected All apps > Create app
- Added the name of app as I want it to appear on Google Play
- Selected a default language: English-US (en-US)
- Specified whether my application is an app or a game: App
- Specified whether my application is free or paid: Free
-
In the "Declarations" section:
- Acknowledged the “Developer Program Policies” declaration
- Accepted the Play App Signing Terms of Service
- Acknowledged the “US export laws” declaration
- Pressed Create app
NOTE: Under "Start testing now" a message displayed: "Release your app early for internal testing without review"
Set up internal testing track:
- Still in Play Console, in left pane, under Release >> Testing, selected Internal Testing
-
Under Testers, clicked "Create email list":
- Entered a List name
- Added testers' email addresses, pressing Enter to submit each
- Pressed Save changes when all testers added
- Entered a Feedback email address
- Pressed Save
Created a new release:
-
Remaining in Play Console, under App bundles, clicked "Choose signing key":
Pressed Use Google-generated key
NOTE: Under App integrity, it now says "Releases signed by Google Play" - Under App bundles, click Upload, navigated to location on local harddrive where release bundle (app-release.aab) was created (e.g., "the project's "build/app/outputs/bundle/release" directory)
- Entered Release details:
- Left Release name as "1 (1.0.0)" (defaulted by Google Play)
- Entered Release notes text
-
Press Next
NOTE: Received warning: "This App Bundle contains native code, and you've not uploaded debug symbols. We recommend you upload a symbol file to make your crashes and ANRs easier to analyze and debug."
Choose to ignore this for now... - Pressed Save
- Under Releases, clicked "View release details" and confirmed "Release summary status" details looked reasonable
- Under Testers >> "How testers can join your test" >> "Join on the web", confirmed that "Testers can join your test on the web" link has updated from a placeholder to a Play Store URL specific to an internal test (e.g., "https://play.google.com/apps/internaltest/...")
- Copied this URL for subsequent usage ([APP_STORE_TEST_URL])
Installed on Android Phone:
NOTE: I am already (effectively "permanently") signed into my Google account on my Android phone; if you're somehow not, you might be prompted to sign in to your Google account during the following sequence.
- On phone browser, navigated to [APP_STORE_TEST_URL]
- Pressed ACCEPT INVITE
- Clicked "Download it on Google Play"
- Clicked "Open in other app"
- Re-directed to Google Play >> "[APP_NAME] (unreviewed)" screen
- Pressed Install
- Pressed Open App
I then navigated to my phone's home screen and could see my app, complete with my custom app icon. I opened it and it ran, as expected (based on experience represented by emulator back in Android Studio).