Analyzing changes in your flutter app using GitHub actions and diffuse
What Is Diffuse
Diffuse is a tool by Jake Wharton for diffing APKs, AABs, AARs, and JARs in a way that aims to provide both a high-level view of what changes along with important detailed output. — https://github.com/JakeWharton/diffuse
It is meant to be used on small changes, such as those that occur in a single PR or git SHA. It is a tool to analyze the changes between two APKs by providing a high-level view of what has changed. With the continuous merging of code into the mainline/repository, the changes to the APK may go unnoticed.
While one is trying to reduce the size of a flutter app or maintain minimal app size, one might want to know what makes up the app in terms of native code, assets…. One could make use of the apkanalyzer in android studio to check such changes but while working in a team, performing such operations via CI/CD seems the best way to go as the changes are checked on every push or pull request, as the case may be. You can check this write on reducing flutter app size.
Diffuse outputs a stream of useful metrics of the changes between two APKs in terms of size and count. Integrating this check in the development cycle can promote higher visibility on the state of the APK as well as provide early feedback to avoid unnecessary problems later on.
Using the Diffuse tool
For the purpose of this article, I will be using the movie listing app available here.
With the help of GitHub Actions, we can execute the Diffuse tool on a Continuous Integration server as part of the development workflow. Say a pull request is made, GitHub Actions will compare the APK of the pull request with the APK on the master branch. As a result, it will output a series of information that can be used to validate the changes to an APK along with other code verification such as code reviews and automated testing.
In this case, I have added some new assets in a new branch here. We want to observe the difference between the APKs once this change has been applied.
Reference
- Master branch (https://github.com/Solidmvp/flurest)
- Pull request (https://github.com/Solidmvp/flurest/pull/1)
- Github action (Diffuse GitHub Action)
Let’s take a look at the GitHub action:
Github Actions
Steps that are executed through GitHub Actions are called workflows. One could create a workflow by either going to the actions tab of a GitHub repository and creating a workflow or by creating a yaml file inside a workflows directory in a .github directory. The path is more like .github/workflows/yourAction.yml.
In this example, a workflow has been created that only runs on pull requests that generate the APKs for both the master and pull request branch. Once the execution is complete, the Diffuse command is used in reference to those APKs. The output will be shown in the real-time logs provided by GitHub Action. Let’s dig into the yaml file.
main.yml
name: Build Client and Compare APK
on:
# push:
# branches: [ master ]
pull_request:
branches: [ master ]
jobs:
buildios:
name: Build iOS app
runs-on: macos-latest
steps:
- uses: actions/checkout@v1
- uses: subosito/flutter-action@v1
with:
flutter-version: '1.17'
channel: 'stable'
- run: flutter pub get
- run: flutter build ios --no-codesignbuild-master:
name: Build master Android app
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
with:
ref: master
- uses: actions/setup-java@v1
with:
java-version: '12.x'
- uses: subosito/flutter-action@v1
with:
flutter-version: '1.17'
channel: 'stable'
- run: flutter pub get
- run: |
flutter build apk
mv build/app/outputs/apk/release/app-release.apk build/app/outputs/apk/release/app-master.apk
- name: Upload master APK
uses: actions/upload-artifact@v1
with:
name: apk
path: build/app/outputs/apk/release/app-master.apkbuild-incoming:
name: Build incoming Android app
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-java@v1
with:
java-version: '12.x'
- uses: subosito/flutter-action@v1
with:
flutter-version: '1.17'
channel: 'stable'
- run: flutter pub get
- run: |
flutter build apk
mv build/app/outputs/apk/release/app-release.apk build/app/outputs/apk/release/app-pr.apk
- name: Upload pr APK
uses: actions/upload-artifact@v1
with:
name: apk
path: build/app/outputs/apk/release/app-pr.apk# Setup Diffuse
setup-diffuse:
name: Setup diffuse
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-java@v1
with:
java-version: '12.x'
- run: git clone https://github.com/JakeWharton/diffuse.git
- name: generate diffuse
run: ./gradlew assemble
working-directory: diffuse
- name: Upload diffuse executable
uses: actions/upload-artifact@v1
with:
name: diff
path: diffuse/diffuse/build/diffuse# Execute Diffuse only when diffuse setup is successful and the two APKs are built successfully
diffuse:
needs: [setup-diffuse, build-master, build-incoming]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Download APKs
uses: actions/download-artifact@v1
with:
name: apk
- name: Download diffuse executable
uses: actions/download-artifact@v1
with:
name: diff
- name: Change diffuse permissions
run: |
ls
chmod +x ./diffuse
working-directory: diff
- name: Execute Diffuse
run: ./diff/diffuse diff apk/app-master.apk apk/app-pr.apk
Since we are building a Cross-platform (flutter) application, we need to build the client on both android and iOS.
The following jobs are carried out:
- buildios: Here, we build the iOS application on the latest available MacOS. We set the flutter version and build the iOS app without code signing.
- build-master: Here, we build the android application off the master branch. We set the java version, flutter version, get our dependencies, and build the apk. We should also upload the apk as an artefact so that we can use it later on when we are trying to compare with the apk on the pull request branch. But wait a minute, how do our GitHub actions differentiate between the master and the incoming branch? We checkout with reference to the master, something like this:
steps:
- uses: actions/checkout@v1
with:
ref: master
- build-incoming: Similar to the build-master job, we set the java version, flutter version, get our dependencies, and build the apk. We should also upload the apk as an artefact so that we can use it later on when we are trying to compare with the apk on the master branch. By default, the GitHub actions build the application of the pull request branch hence no need to set a reference.
Note: One should note that the apk is always named app-release.apk and is located in the path build/app/outputs/apk/release. Since this applies to both the master branch and the incoming, we should distinguish them. How do we go about that? We could rename the apks accordingly as shown below:
build-master job:
- run: |
flutter build apk
mv build/app/outputs/apk/release/app-release.apk build/app/outputs/apk/release/app-master.apk
build-incoming job:
- run: |
flutter build apk
mv build/app/outputs/apk/release/app-release.apk build/app/outputs/apk/release/app-pr.apk
As we can see in the above, we have named the apk for the master branch app-master.apk and that of the incoming to be app-pr.apk. Cool right? 😁😁😁. We should not forget to upload the apks as an artefact also.
- setup-diffuse: Diffuse is a tool by Jake Wharton for diffing apks. You can check the tool here. We set up the diffuse executable by git cloning the project, generate the diffuse executable with the command
./gradlew assemble
and uploading the diffuse executable as an artefact.
Now the moment we have been waiting for: comparing the apks
- diffuse: We specify this action to depend on setup-diffuse, build-master, build-incoming hence this job won’t run if either of setup-diffuse, build-master, build-incoming fails. This job runs on the latest ubuntu-version. We download the APKs and the diffuse executable. We should also be sure to change the diffuse permission to make it executable and then run the diffuse command to compare both APKs.
- name: Change diffuse permissions
run: |
chmod +x ./diffuse
working-directory: diff
- name: Execute Diffuse
run: ./diff/diffuse diff apk/app-master.apk apk/app-pr.apk
And there we have it. The output will be shown in the real-time logs provided by GitHub Action.
Diffuse provides a high-level view comparing both APKs in their compressed and uncompressed form. It also tells us what part of the APK has been significantly changed. We also get information regarding the APK, types, methods, and fields all in details. That’s a lot of info right 🙂.
As shown in the image above, there is a change in the assets signified under path as assets/flutter_assets/assets/fonts/Arvo-Bold.ttf between the two APKs. You can check the diffuse action here.
Bonus
While pushing your app to Github, you should not push your API Key. Say, for instance, you are running some GitHub actions to build the application on push or pull request, the application build will surely fail as it can’t find the API key.
So what do you do? GitHub has a service called Secrets. Every repository has its secrets. You should add your API key. That begs the question “how will the application/GitHub actions know where to find the key”.
You should edit your apiKey.dart file to match the below, assuming you saved the secrets as api_key
apiKey.dart
import 'dart:io' show Platform;
String apiKey = Platform.environment['api_key'];
// we comment the previous line our
// String apiKey = "Your_API_KEY";
This way the API key is called as an environment variable since all Secrets of a repository are treated as environment variables available to the repo even though they are not available at runtime. Also, no one has access to a repo secret except the owner hence even if someone clones your code or forks it, the secret won’t be available to them. It would be as though there was no API key.
By the way, top Flutter engineers around the globe love our tweets. We think you will love them too because our tweets include delicious topics in Flutter! Use the link below to join our Twitter community!
Other Useful Resources
Reducing Flutter App Size
Diffuse Repository
Diffusing APKs
The project used in this article can be found below: