Building a Flutter application for Web, iOS and Android using a CI/CD pipeline on CodeBuild – #cdk4j

This post is a follow up to the last one where I showed a CDK project that can be used to build a Flutter application for Web.

In this post, we are going to expand our existing project on Github to be able to build an “apk” file for Android and a zip file for iOS. Before I can show you how this is possible, let’s start with some challenges that I’ve faced šŸ™‚

The aim of this CI/CD pipeline is (not yet) to be able to push the apps into the AppStore / PlayStore for testing. That’s something we can add later šŸ˜‰

Challenges on the way to a full CI/CD pipeline for Flutter on CodeBuild

While preparing this post I unfortunately faced more problems building up the pipeline than expected. Several problems.

AWS CodePipeline does not support M1 / macOS build images

Currently, AWS CodePipeline unfortunately does not provide the possibility to use the famous M1 minis on AWS as CodeBuild images. Tis his a real problem, as is makes it impossible to use CodeBuild for building iOS apps.
Running XCode on macOS is a requirement for building a Flutter app for iOS.
The M1 minis on AWS are currently pricey as hell for this use case – if you start ONE build you are directly charged 24 hours, even if the build takes only a few minutes! You need to actual get a dedicated instance, … – not usable for our use case of quickly building something for a side-project.
So we needed to find an alternative… read below! šŸ˜‰

The current AWS CodePipeline standard runtimes are not able to build (modern) Android applications

The runtimes available and exposed by CodePipeline support Android runtime 29 – and the Docker images are provisioned using Java 8. Unfortunately, as of July 2021, the Android gradle tools (used by Flutter) require Java 11. I have created an issue in the corresponding Github (see here) but needed to find a workaround to move on – I think I’ve found one, but I hope that anyone reading this might have a better way or idea?

TypeScript dependencies on AWS Lambda can take your sleep

#awscommunity helps!

When implementing the trigger for the iOS App build (see more details below) I decided to “quickly” implement the HTTPS POST call using TypeScript – which turned out to be a bad decision šŸ™‚
I had trouble getting the “axios” dependency that I am using installed correctly. I asked around, especially my fellow AWS Community Builders and got a lot of great tips and ideas (kudos to Martin and Matt). Martin had the right “stomach feeling – I was missing a “npm install”.

Matt enlightened me with the three different possibilities of making Typescript Lambda functions understand their dependencies:

1. Bundle dependency with your source code (can be achieved using esbuild)

2. Add a package.json and node_modules to the lambda function source – only a good idea if dependencies cannot be minified

3. Put the dependencies in a lambda layer

Matt Morgan

At the end, this challenge was especially difficult because I needed to add the required “npm install” in two places: In the “installCommands” for the CodePipeline itself and in the “installCommands” for the Flutter build step.

CodeBuild is slow, misses conditional steps and misses integrations – and does not easily allow multi-branch pipelines

While implementing the pipeline and solving the different challenges mentioned above, I lost some time because of CodeBuild being “slow” (>1min wait time during provisioining of the build containers). Thats understandable given the nature of the service, however it would be cool to have something like a “warm start” for a pipeline where the containers are re-used instead of re-provisioned.

There are no conditional steps – no chance to run a job only based on environment variables or anything similar. That made me implement a workaround. It would be cool to be able to use something like “branch-conditions” in the way Jenkins offers it.

CodeBuild offers only basic integration to SNS, but you cannot integrated a “lambda build step” to run the CodeMagic integration i nparallel to the flutter build job, but that is not possible, so I needed to run this “at the end” of the pipeline.

Another thing I’d love to have: multibranch pipelines. I needed to merge everything to main directly in order to test, because I couldnt figure out how my CDKPipeline would be able toe support multiple branches.

Reaching the goal: a full CI/CD pipeline running on AWS CodeBuild to build a Flutter app for Web, Android and iOS

Here is a diagram of the “final result” that I am presenting today:

Overview picture for CI/CD pipeline for Flutter App publishing to Web, Android APK and iOS zip

The “output” artifacts of our pipeline are:
– Flutter Web application (located on S3 and reachable through HTTP call)
– Flutter Android APK (that can be side-loaded on Android phones, located on S3 bucket)
– Flutter iOS App (that can be side-loaded on iOS phones, located within CodeMagic)

As the diagram shows, we needed to fall back to an 3rd-party, non-AWS service to be able to package the iOS application. After doing a quick “vendor selection” and a shortlist that included Bitrise and CodeMagic I decided to integrate CodeMagic in this example – because I liked the API more and it offers more free build credits/minutes. Setting it up took less then 5 minutes – it connects natively to Github and the set up of the Flutter pipeline is very easy.
The integration is set up using a Lambda function that calls the “start build” API.

How did I solve the challenges mentioned above?

The problem building the iOS image was resolved by integrating the external Service CodeMagic.

The Android Runtime Dependencies problems with Java 11 was resolved by switching to a custom docker container (Open source) – and then installing the requirements on top of it (npm/node, awscli, etc.).

What did you learn in this post?

In this post you have learned on how to expand the implementation of our CI/CD pipeline for an example Flutter application to not only building a “web” application, but also building an Android APK and an iOS zip file.
You have also seen an extension and integration of the Codepipeline with SNS for notifications and those events being picked up by a lambda function to trigger an external HTTPs API.
This is a major step – with this pipeline we are able to publish our application for three different “platforms” without a single code change – and it will all happen completely automatically!

Iā€™d be glad to get your inputs into my Github repository as a pull request or just as comments on the project itself.

Next steps

Further expansions needed to this project:
– CodePipeline already has a SNS topic that it reports to – but right now the build iOS / Android App packages are not exposed anywhere – the idea would be to publish to an SQS queue the name of the APK file and the CodeMagic Build Id – and have a lambda function that is triggered by the queue update a link on the example application to download the newest version of the app šŸ˜‰ Today, we need to retrieve both from S3 / CodeMagic itself
ā€“ use the CloudFormation Exports of the Lambda functions in the Flutter application instead of hardcoding the URLs for the Lambda Function URLs- enhance security for Lambda Function URLs
ā€“ add CloudFront in front of S3 to allow HTTPs connections to the Flutter App
ā€“ enhance CI/CD pipeline to package Windows App using Flutter
– enhance CI/CD pipeline to push created apps to App Store / Play Store

Feel free to contribute and add your contributions to this project into my Github repository.

Building a Flutter application (for Web) with AWS Lambda Function URL backend using AWS CDK Pipelines (written in Java)

Wow, this is a long title šŸ˜‰

In this post I am going to use CDK Pipelines to build a demo Flutter application hosted on AWS S3 with a Backend powered by AWS Lambda (using Function URLs).
The CDK code will be in Java, the Lambda functions in Typescript and the WebApp in Dart. Why? Because I love trying out things šŸ™‚

The code used here is not production ready and does not fulfill required security best practices! šŸ™‚

The CI/CD pipeline

The CI/CD pipeline for this project uses CDK Pipelines and that means it is build on top of AWS CodePipeline (which under the hood uses CodeBuild, CodeStar and other services to be functional).

It consists of different stages that build and deploy the corresponding part of the application:
– one stage for each lambda function
– one stage for the build and deployment of the Flutter application

The stages required by the CDK Pipeline to update itself are automatically added but not part of our code:

public CdkPipelineStack(final Construct parent, final String id, final String branch, final StackProps props) { super(parent, id, props); String connectionArn = "arn:aws:codestar-connections:eu-central-1:xxx:connection/xxx"; final CodePipeline pipeline = CodePipeline.Builder.create(this, getCodepipelineName(branch)) .pipelineName(getCodepipelineName(branch)).dockerEnabledForSynth(true) .synth(CodeBuildStep.Builder.create("SynthStep") .input(CodePipelineSource.connection("lock128/cdk-codepipeline-flutter", branch, ConnectionSourceOptions.builder().connectionArn(connectionArn).build())) .installCommands(List.of("npm install -g aws-cdk" // Commands to run before build )).commands(List.of("mvn test", "mvn package", // Language-specific build commands "npx cdk synth", // Synth command (always same) "bash start_codecov.sh")) .build()) .build(); pipeline.addStage(new CheckAgeLambdaStage(this, "DeployCheckAgeLambda"), getCheckAgeStageOpts()); pipeline.addStage(new PaperSizeStage(this, "DeployPaperSizeStage"), getPaperSizeStageOpts()); pipeline.addStage(new CalculatorStage(this, "DeployCalculatorStage"), getCalculatorStageOpts()); PolicyStatement flutterDeployPermission = getDeployPermissions(); CodeBuildStep buildAndDeployManual = CodeBuildStep.Builder.create("Execute Flutter Build and CodeCov") .commands(getFlutterBuildShellSteps()).rolePolicyStatements(Arrays.asList(flutterDeployPermission)) .build(); pipeline.addStage(new FlutterBuildStage(this, "FlutterBuildStage"), getFlutterStageOptions(buildAndDeployManual)); }
Code language: Java (java)

This is the definition of the CI/CD pipeline, it uses our Github repository as the source of the code and automatically starts after a push to the repository on the main branch.
The “Flutter Build Stage” is the one that currently builds the Flutter web application, deploys it and makes it available to the end user. Going forward, to make best use of Flutter, we would need to expand this stage to also be able to build an iOS application, an android application or an application for any other platform supported by Flutter. As a “goal” I would personally also want to extend this stage to be able to publish the apps to the corresponding stores (App Store, Play Store, Windows Store, …) – Thanks to my friends at cosee for the help and guidance around this process!

The architecture diagram of the application

What we are using to show-case the usage of AWS CodePipeline, Flutter as an application and AWS Lambda Function URLs as backend is not really an “application” – but it can do dynamic things and it can easily be extended to include a database backend, etc.

Infrastructure as Code using AWS CDK in Java

In this section you are going to have a look on the CDK code required to provision the infrastructure on AWS.
We are using AWS CDK written in Java – and because of that Maven as a build tool (Maven is the default tool for CDK projects – I’ve already used Gradle as build tool and that works in the same way.

The possibility of writing Infrastructure Code in Java is a great thing because it gives us the option to build on top of our existing skills – and I’ve written enough lines of Java in my career to feel comfortable using it to provision the infrastructure.
This is one of the best things in CDK: You can write your Infrastructure code in Java, Typescript, Python, … – and that helps us to build teams that only have “one” language as a “core skill” – one team might choose to develop in Java, anotherone in Typescript, another team could use Go – this allows the teams to build up mastery in a specific language!

In our example however, we are not making use of this possibility – I’ve choosen to rather go the opossite way: Combine a few languages, just to show that it works šŸ˜‰

Stacks in the Example Project

The CDK code consists of four “stacks” – each of the Stack representing one “component” in this application.
In our example these four stacks are part of one CDK Application & one CodePipeline. In bigger projects, you might want to split these out into seperate applications – which introduces a lot tof things to consider (e.g. how do they fit and work together, etc.).

While writing this post, the four stacks combined are 129 lines of source code. With the help of the CDK Constructs that are being used this translates to over 1k lines of code in CloudFormation. We are only using L2 constructs here – there is way more constructs available that you can use in the Constructs Hub and also a lot of guidance regarding the usage of CDK over at CDKPatterns.

CDK Stack that provisions the infrastructure for the Flutter hosted website

Making a S3 bucket available to host our Flutter application becomes a very short piece of Java code as shown in the picture above.

Lambda functions behind Function URLs in Typescript and Python

This was definately one of the most awaited announcements in the Serverless space this year: The GA of the “Function URLs” for Lambda on AWS – and this is the reason for me using these as a backend in this showcase.
With this announcment, it is possible to directly expose your application running on AWS Lambda behind an HTTPs endpoint! Without an ApiGateway, Proxy, …
Provisioning the infrastructure for the lambda function with the function URL functionality activated is only a few lines in CDK:

CDK Stack for the Check Age Lambda function with the activation of the Function URL

These Lambda functions will now horizontally scale without us as a developer getting involved. More details on function URLs – there is a lot of good posts on it around like this one or this one.
Around scalability, fellow community builder Vlad has written up a great guide around scalability for containers on his website.

Web Single Page Application built using Flutter and the benefits of using Flutter

Flutter as a multi-platform, developer driven tool gives a lot of flexibility. With a single code base, you are able to publish your application to various targets. In this example, we are using the target platform “web” – which compiles the Dart code to a single page application.

This application is automatically aware of the size of your screen and is interative – which is another cool thing that Flutter takes away from you as a developer. A lot of organizations use Flutter today and the cookbook gives you an easy and good start into developing Flutter applications.

Our example application has three input fields that take input and pass it back to our Function URLs – and then automatically update a Text Widget with the results of the Function URL call. This implementation will work on all platforms.

What did you learn in this post?

You have learned how easy it is to provision AWS infrastructure using AWS CDK. You have also seen that you can easily combine different programming languages in a single CDK application and got an experience on how a CI/CD pipeline can help to automatically deploy our application using AWS CodeBuild.
In addition to that, you have looked at Flutter as a multi-platform development tool.

I’d be glad to get your inputs into my Github repository as a pull request or just as comments on the project itself.

Next steps

Further expansions needed to this project:
– use the CloudFormation Exports of the Lambda functions in the Flutter application instead of hardcoding the URLs for the Lambda Function URLs- enhance security for Lambda Function URLs
– add CloudFront in front of S3 to allow HTTPs connections to the Flutter App
– enhance CI/CD pipeline to package Android App using Flutter
– enhance CI/CD pipeline to package iOS App using Flutter
– enhance CI/CD pipeline to package Windows App using Flutter
Feel free to contribute and add your contributions to this project.

Listen to Werner Vogels talking about the benefits of CDK