# Fastlane - Google Play

When a Flutter app starts having many releases, manually building AAB and uploading to Google Play Console is very easy to get wrong. I use **Fastlane** to automate the full Android release flow.

My goals are:

* Reduce manual steps when shipping a new version.
* Standardize the process for the whole team.
* Clearly separate lanes: `internal`, `alpha`, `production`.

## Prepare the Fastlane environment

### Step 1: Install Bundler

```bash
gem install bundler
```

### Step 2: Create `Gemfile`

Create a `Gemfile` at project root:

```ruby
source "https://rubygems.org"
gem "fastlane"
```

### Step 3: Install local dependencies

```bash
bundle config set --local path 'vendor/bundle'
bundle install
```

I prefer this way to lock Fastlane version per project.

## Create a service account for Google Play API

### Step 1: Create a service account in Google Cloud

Go to: [Google Cloud Service Accounts](https://console.cloud.google.com/iam-admin/serviceaccounts)

Do the following:

* Select or create a new project.
* Create a service account named `fastlane-deploy`.
* Assign role `Editor` (for production, consider least-privilege roles).
* Go to `Keys` -> `Add key` -> select `JSON` to download the key.
* Copy the service account email (format `xxx@xxx.iam.gserviceaccount.com`).

### Step 2: Grant permissions in Google Play Console

Go to: [Google Play Console](https://play.google.com/console)

Open `Google Play Console -> Users and permissions -> Invite new user`:

* Paste the service account email.
* Grant the proper release permissions.
* No email confirmation is required.

## Put the JSON key into the project

Put the JSON file in project, for example:

```
android/fastlane/play-service-account.json
```

If you want better security, add this to `.gitignore`:

```gitignore
android/fastlane/*.json
```

## Initialize Fastlane for Android

Go to `android` folder and run:

```bash
cd android
fastlane init
```

When prompted, fill in:

* Package name: `com.example.app`
* JSON path: for example `./android/fastlane/play-service-account.json`
* `Download existing metadata and setup metadata management?` -> `y`

After this step, Fastlane will create `Fastfile` and `Appfile`.

## Configure `Fastfile`

Edit `android/fastlane/Fastfile` like this:

```ruby
default_platform(:android)

platform :android do

  ########################################
  # INTERNAL TRACK (not public)
  ########################################
  desc "Upload to internal testing"
  lane :internal do
    # Build AAB
    gradle(
      task: "bundle",
      build_type: "Release"
    )

    # Upload to Google Play
    upload_to_play_store(
      track: "internal",
      json_key: "fastlane/play-service-account.json",
      package_name: "com.example.app",
      aab: "app/build/outputs/bundle/release/app-release.aab"
    )
  end

  ########################################
  # CLOSED TEST (alpha)
  ########################################
  desc "Upload to closed testing"
  lane :alpha do
    upload_to_play_store(
      track: "alpha",
      json_key: "fastlane/play-service-account.json",
      package_name: "com.example.app",
      aab: "app/build/outputs/bundle/release/app-release.aab"
    )
  end

  ########################################
  # PRODUCTION (public store)
  ########################################
  desc "Upload to production"
  lane :production do
    upload_to_play_store(
      track: "production",
      release_status: "completed",     # Auto publish
      json_key: "fastlane/play-service-account.json",
      package_name: "com.example.app",
      aab: "app/build/outputs/bundle/release/app-release.aab"
    )
  end

end
```

## Run release lanes in `android` folder

```bash
fastlane internal
fastlane alpha
fastlane production
```

Quick explanation:

* `internal`: upload to internal track (not public).
* `alpha`: upload to closed testing.
* `production`: upload to public store.

## Fix common issue: Fastlane cannot build AAB because Java is not aligned

### Sync JDK for Flutter and Fastlane

Issue I faced:

* `flutter build appbundle` works.
* But `fastlane` build fails because Java versions are different.

Fix (example using Java 17 on macOS Apple Silicon):

```bash
brew install openjdk@17
sudo ln -sfn /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-17.jdk
```

Add to `~/.zshrc`:

```bash
export JAVA_HOME="/opt/homebrew/opt/openjdk@17"
export PATH="$JAVA_HOME/bin:$PATH"
```

Reload shell and check:

```bash
source ~/.zshrc
java -version
```

Expected result: `openjdk version "17.x.x"`.

Run Fastlane again:

```bash
cd android
fastlane internal
```

If running from project root, you can use:

```bash
fastlane android internal
```

## Additional notes

* For iOS, I use `devices.txt` to manage test devices and auto-add devices in Fastlane flow.
* This article focuses on Android/Google Play, I will publish a separate iOS article next time.

## Conclusion

Fastlane helps me turn a release process with many manual steps into a few fixed commands. After setup, for each new release I only need to bump version and run the corresponding lane.

It becomes much faster if you combine it with Make. If you do not know it yet, check this article.

{% content-ref url="../easy-code/make" %}
[make](https://wong-coupon.gitbook.io/flutter/easy-code/make)
{% endcontent-ref %}

[Buy Me a Coffee](https://buymeacoffee.com/ducmng12g) | [Support Me on Ko-fi](https://ko-fi.com/I2I81AEJG8)
