We are in a state where companies are releasing software and solutions within minutes, and they are doing so by following the Continuous integration (CI) and continuous delivery (CD) set of operating principles.
A CI/CD pipeline makes the automatic delivery of your software more frequent, reliable, and secure. It focuses on higher code quality, and that’s why it is vital for a mobile developer or team.
In this tutorial, you’ll learn how to deploy your Flutter app following CI/CD principles with GitHub Actions as a tool.
This tutorial requires a Google service account, which will be used in GitHub Actions to publish the Android build to the Play Store, so to create a project in GCP, create a service account and select your created project.
N.B., this tutorial assumes you have some prior knowledge of Flutter. If you are new to Flutter, please go through the official documentation to learn about it.
What is GitHub Actions?
GitHub Actions is a CI/CD tool that helps you build, test, and deploy your changes on production directly from your repository. You can use it to set up app releases on certain events like committing a tag in a certain branch of your repository.
Additionally, one doesn’t have to create a workflow for common things across the projects as GitHub has a marketplace from which you can use existing workflows developed by others.
The GitHub Actions workflow uses .yml
(“YAML Ain’t Markup Language”) files, which will be stored in the .github
directory at the root of your project.
Additionally, GitHub Actions supports different environments and containers like Linux, macOS, Windows, and even VMS.
Getting started
Follow the below steps for initial setup:
- Set up a new Flutter project using your favourite IDE or using the Flutter command-line tool
- Initialize Git in your new project on your machine and create a new repository associated with your GitHub account
- Create the config directory in the root of your flutter project
.github
and a new directory calledworkflows
. The workflows here will contain all your CI/CD workflows as.yml
files
Use a basic Flutter action to build an Android release
Now, you will create a basic Android workflow to help you understand how GitHub Actions works in building your Flutter app.
Create a .yml
file, android-release.yml
, inside workflows
with the following code:
name: Android Release # 1 on: # 2 push: branches: [ "master" ] pull_request: branches: [ "master" ] # 3 workflow_dispatch: # 4 jobs: # 5 build: # 6 runs-on: ubuntu-latest # 7 steps: # 8 - uses: actions/checkout@v3 # 9 - uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: "12.x" # 10 - uses: subosito/flutter-action@v2 with: # 11 flutter-version: "3.0.0" channel: 'stable' # 12 - name: Get dependencies run: flutter pub get # Runs a set of commands using the runners shell - name: Start release build run: flutter build appbundle
The above workflow:
- Controls when the workflow will run
- Triggers the workflow on push or pull request events for the
"master"
branch; you can change it according to your requirement - Allows you to run this workflow manually from the Actions tab from your GitHub repo (a workflow run is made up of one or more jobs that can run sequentially or in paralle)l
- Contains a single job called
build
- Contains the type of runner that the job will run on
- Uses steps to represent a sequence of tasks that will be executed as part of job
- Readies your repository under
$GITHUB_WORKSPACE
, so your job can access it - Sets up Java so your job can use it for the Flutter app build
- Sets up Flutter using the subosito Flutter workflow
- Adjusts to the Flutter version you are working with
- Runs a single command using the runner’s shell
Problems with this basic action
The problem with this basic workflow is that whenever you push changes in the master
branch, this workflow will trigger and start setting up the Java SDK and Flutter SDK every time. So eventually, it will lead to the latency of building your application as you have to set up services every time.
How can you make your workflow faster?
You can make your Flutter workflow faster by caching the Java and Flutter SDKs so that on the next run, it won’t fetch the SDK directly before checking for the existence of the SDKs.
In your main.yml
file, make the following changes:
- uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: "12.x" cache: 'gradle' // 1 - uses: subosito/flutter-action@v2 with: flutter-version: "3.0.0" channel: 'stable' cache: true // 2
You have updated the SDK setup by providing Gradle to be cached with respect to the Java SDK (1
) and enabling caching for the Flutter SDK (2
).
Next time you run the job post, save the above changes and observe the time spent; you will see a time difference from the basic flow.
Prepare for the Play Store release
Now you will be expanding your workflow to create an Android Play Store release.
Generate a version number
For any new release, you should have a new release version number, so before the build, you need to create a version number using the below job:
# 1 version: name: Create version number # The type of runner that the job will run on runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job steps: - uses: actions/checkout@v3 # 2 - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0.9.7 with: versionSpec: "5.x" - name: Use GitVersion id: gitversion uses: gittools/actions/gitversion/execute@v0.9.7 # 3 - name: Create version.txt with nuGetVersion run: echo $ > version.txt # 4 - name: Upload version.txt uses: actions/upload-artifact@v2 with: name: gitversion path: version.txt
In the above code, we did the following:
- Created a new job version that will be executed before the build job
- Installed the GitVersion, a tool used for versioning by looking at your Git history
- Posted using GitVersion, placing the version in a
version.text
file - Uploaded the
version.text
file as an artifact for the actions system with a namegitversion
to be used later in the build job
Sign the app
To publish the app to Play Store, you need to give your app a digital signature using a keystore. Follow this official Flutter Doc on how to do that depending upon your machine:
keytool -genkey -v -keystore %userprofile%\upload-keystore.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias upload
This will store a file with a .jks
extension in your home directory or whatever path you provided.
N.B., make sure to add the store password, key password, and key alias in your GitHub repository secrets (from GitHub repository > Secrets > Actions)
Facing issues while running the keytool?
If you are facing “’keytool’ is not recognized as an internal or external command” issue, then add the path of the JDK bin to use environment variables, or else install JDK and repeat the path addition to environment variables.
Next, create a new file key.properties
under the Android directory of your app and provide the reference to your keystore generated before:
storePassword=<password from previous step> keyPassword=<password from previous step> keyAlias=upload storeFile=<location of the key store file, such as /Users/<user name>/your-keystore-file.jks>
To use this key when building your app in release mode, update your Android-level build.gradle
file as below:
- Define the
keyProperties
variable to refer to thekey.properties
file from thefilesystem
:def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) }
- Update the
buildTypes
and add thesigningConfigs
as below:signingConfigs { release { keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null storePassword keystoreProperties['storePassword'] } } buildTypes { release { signingConfig signingConfigs.release } }
After this, your new build will be created in release mode using the key.
N.B., don’t commit the keystore
key and the key.properties
file and let them be private.
So you might be wondering, how can our job know whether this key exists in the filesystem and use it as a reference in the key.properties
file?
To resolve this, do the following:
Base64
encode your keystore file in your machine using Git Bash or Bash:base64 <your-keystore-file.jks>
- Create a new secret
ANDROID_KEYSTORE_BASE64
in your GitHub repository - Copy the output and paste it as
ANDROID_KEYSTORE_BASE64
in your GitHub repository; it’ll remain safe there
Now update the build job in your android-release.yml
file:
build: name: Create Android Build # 1 needs: version runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 # 2 - name: Get version.txt uses: actions/download-artifact@v2 with: name: gitversion # 3 - name: Create new file without newline char from version.txt run: tr -d '\n' < version.txt > version1.txt # 4 - name: Read version id: version uses: juliangruber/read-file-action@v1 with: path: version1.txt # 5 - name: Update version in YAML run: sed -i 's/99.99.99+99/$+$/g' pubspec.yaml # 6 - name: Download Android keystore id: android_keystore uses: timheuer/base64-to-file@v1.0.3 with: fileName: upload-keystore.jks encodedString: $ # 7 - name: Create key.properties run: | echo "storeFile=$" > android/key.properties echo "storePassword=$" >> android/key.properties echo "keyPassword=$" >> android/key.properties echo "keyAlias=$" >> android/key.properties - uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: "12.x" cache: gradle - uses: subosito/flutter-action@v2 with: flutter-version: "3.0.0" channel: 'stable' cache: true - name: Get dependencies run: flutter pub get - name: Start Android Release Build run: flutter build appbundle # 8 - name: Upload Android Release uses: actions/upload-artifact@v2 with: name: android-release path: build/app/outputs/bundle/release/app-release.aab
In the above code, you performed the following:
- Added dependency on the version job to run this one sequentially
- Downloaded the version file uploaded in the first job using the name
gitversion
- Created a new file without
newline char
fromversion.txt
- Read the updated version from
version1.txt
file - Updated the
pubspec.yml
file with the version ID having the version in it - Decoded the
base64
encoded keystore value saved as a secret to IDandroid_keystore
- Created
key.properties
using the secrets andandroid_keystore
- Uploaded the Android release bundle as an artifact to be used in the next job
Deploy the app
Now, you need to use the bundle and send it to Play Store. Before that, it is time to make use of the service account that you created at the start of this tutorial. If the service account is created, copy the key for that account and store it in secrets as PLAYSTORE_ACCOUNT_KEY
.
Next, in your Google Play Console > Users & Permissions, invite the user and add the service account user email here.
If you are not seeing your app in App permissions, make sure that the Google Play Developer API in GCP is enabled for your project.
Next, update the permission of the user so that it has the access to release the app like the admin role.
Now, add a new job deploy in your android-release
flow:
deploy: name: Deploy Android Build # 1 needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 # 2 - name: Get Android Build from artifacts uses: actions/download-artifact@v2 with: name: android-release # 3 - name: Release Build to internal track uses: r0adkll/upload-google-play@v1 with: serviceAccountJsonPlainText: $ packageName: <YOUR_PACKAGE_NAME> releaseFiles: app-release.aab track: alpha status: completed
Here, you did the following:
- Added a dependency to run this job sequentially
- Downloaded the Android build from artificats using the name
android-release
- Used the upload-google.play@v1 workflow with the
PLAYSTORE_ACCOUNT_KEY
secret, your app package name, the track in which you want to upload the build and its status
After this, push your changes to GitHub and see the workflow deploy your app to the Play Store.
Here’s the complete workflow:
name: Android Release on: push: branches: [ "master" ] pull_request: branches: [ "master" ] workflow_dispatch: jobs: version: name: Create version number runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0.9.7 with: versionSpec: "5.x" - name: Use GitVersion id: gitversion uses: gittools/actions/gitversion/execute@v0.9.7 - name: Create version.txt with nuGetVersion run: echo $ > version.txt - name: Upload version.txt uses: actions/upload-artifact@v2 with: name: gitversion path: version.txt build: name: Create Android Build needs: version runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Get version.txt uses: actions/download-artifact@v2 with: name: gitversion - name: Create new file without newline char from version.txt run: tr -d '\n' < version.txt > version1.txt - name: Read version id: version uses: juliangruber/read-file-action@v1 with: path: version1.txt - name: Update version in YAML run: sed -i 's/99.99.99+99/$+$/g' pubspec.yaml - name: Download Android keystore id: android_keystore uses: timheuer/base64-to-file@v1.0.3 with: fileName: upload-keystore.jks encodedString: $ - name: Create key.properties run: | echo "storeFile=$" > android/key.properties echo "storePassword=$" >> android/key.properties echo "keyPassword=$" >> android/key.properties echo "keyAlias=$" >> android/key.properties - uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: "12.x" cache: gradle - uses: subosito/flutter-action@v2 with: flutter-version: "3.0.0" channel: 'stable' cache: true - name: Get dependencies run: flutter pub get - name: Start Android Release Build run: flutter build appbundle - name: Upload Android Release uses: actions/upload-artifact@v2 with: name: android-release path: build/app/outputs/bundle/release/app-release.aab deploy: name: Deploy Android Build needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Get Android Build from artifacts uses: actions/download-artifact@v2 with: name: android-release - name: Release Build to internal track uses: r0adkll/upload-google-play@v1 with: serviceAccountJsonPlainText: $ packageName: <YOUR_PACKAGE_NAME> releaseFiles: app-release.aab track: alpha status: completed
Note:
- You can combine all these jobs in a single and only job so sharing files between jobs won’t require artifacts publishing, which consumes a free usage limit
- There is a known issue that sometimes the app is not published on the initial run. So, upload an APKor appbundle built from this pipeline and roll it out for internal users. After that, this workflow will be able to release apps without any issues
- If you are still facing issues during deployment, make sure all config and permissions are correct, or check this issues page
Flutter web release to GitHub pages
Now create a new web-release.yml
workflow and paste the following code:
name: Web Release on: push: branches: [ "master" ] pull_request: branches: [ "master" ] workflow_dispatch: jobs: build: name: Create Web Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: "12.x" cache: gradle - uses: subosito/flutter-action@v2 with: flutter-version: "3.0.0" channel: 'stable' cache: true - name: Get dependencies run: flutter pub get - name: Start Web Release Build run: flutter build web --release - name: Upload Web Build Files uses: actions/upload-artifact@v2 with: name: web-release path: ./build/web deploy: name: Deploy Web Build needs: build runs-on: ubuntu-latest steps: - name: Download Web Release uses: actions/download-artifact@v2 with: name: web-release - name: Deploy to gh-pages uses: peaceiris/actions-gh-pages@v3 with: github_token: $ publish_dir: ./
The above workflow is quite similar to the Android workflow, but here you are using the Flutter web build
command and later using the peaceiris/actions-gh-pages@v3 workflow to deploy the web build to GitHub Pages.
Note:
- The
GITHUB_TOKEN
is not a personal access token. It gets automatically created to authenticate in your workflow - Make sure the branch in the GitHub Pages section in Settings is set to
gh-pages
Conclusion
In this tutorial, you learned about how to set up a GitHub Actions workflow to deploy your Flutter app across the Web and Android. For the next step, you can copy and modify the workflow to directly release the app to the app store or learn about other alternatives of GitHub Actions like CircleCI, GitLab CI, Jenkins, and more.
We hope you enjoyed this tutorial. Feel free to reach out to us if you have any queries. Thank you!
The post Flutter CI/CD using GitHub Actions appeared first on LogRocket Blog.
from LogRocket Blog https://ift.tt/WjEdQKc
Gain $200 in a week
via Read more