Native App Engineering @ amaysim - Our Top 5 Accelerators
Over the past 2.5 years, I've been fortunate enough to lead the apps engineering team here at amaysim (now part of the Optus family). I'm super proud of this team, which has developed one of the highest-rated telco apps in Australia.
In collaboration with other teams at amaysim, we've delivered a steady stream of new features; allowing customers to bank unused data, activate a retail SIM in-app, sign up with an eSIM, multi-factor authentication with biometrics and so much more.
The pace of delivery has been one of the fastest I've experienced, while maintaining a high level of quality. People & Culture has played a significant role in this, but it's a Technology & Practice lens that I'd like to use in this post, taking a brief look at 5 areas that have acted as accelerators for app delivery:
Cross-platform adoption, with Flutter
Design language and style guide
Automated & frequent releases, with Github actions
Native app observability, with NewRelic
Full stack, cloud native
1. Cross-platform adoption, with Flutter
Like many mobile teams, we were struggling with the challenges that come with native app development - time & cost of building features twice across both iOS and Android, maintaining feature parity, and challenge in scaling the team given the specialist skillsets required. So after a period of evaluation and exploration, we decided to move from pure native development to use Flutter, a cross-platform tool for native app development.
Flutter has been a game-changer for us, in many ways:
Productivity - using "Hot reload", a feature Flutter provides to reduce the development cycle, code changes are reflected in a running app, instantly. This is significantly faster than the code-compile-launch cycle, that is typical of native app development. Combine this with the ability to debug, simultaneously across multiple devices & operating systems, with confidence that Flutter renders consistently across platform, Flutter has been a great accelerator for development.
Reuse - Flutter has a rich library of "widgets" to quickly compose user interfaces. For example, animations and transitions, which developers would traditionally spend days refining with native SDKs are instead composed from a library of existing assets from Flutter. Flutter also has a large, and growing package library, authored by Google engineering teams and the open source community, which we leverage.
Quality - automated testing of native apps is typically slow and brittle, due to the need to interact with simulators or real devices. Flutter golden tests allow us to write automated tests that exercise the UI, without the need of a simulator or device. These tests are fast and reliable and offer an increased level of confidence, by performing visual regression tests. Combined with static code analysis, unit testing, debugging and profiling tools, and CI/CD support, Flutter has provided us with the toolkit needed to build high quality apps.
Easy to adopt - Flutter apps are written in the Dart programming language, which share many language features with Javascript. Flutter also shares several concepts familiar to web developers, such as a reactive paradigm, components and common layout model, which reduces the learning curve for web developers coming to Flutter. In comparison to up-skilling with native technologies, Flutter has given us a much more accessible tech stack.
Opportunity to scale - with the accessibility of Flutter to web developers, we are able to scale our development team by utilising a skillset amaysim already has, to scale up/down our apps engineering team as needed. Flutter also provides opportunity to scale in an other sense, to deploy the same self-service app experience on desktop & web.
2. Design language and style guide
Sharing a common design language with our Design & UX teams has helped to focus design effort on what really matters. There's no need to reinvent UI for each new feature. We apply our design language to UX wireframes to reduce effort and maximise reuse of existing elements.
Flutter made it easy for us to implement our design language into a style guide, which documents and demonstrates the reusable UI components we use to build our apps.
Most of our components apply branding and custom behaviour on the extensive widget library Flutter provides. Flutter does the heavy lifting by providing highly configurable UI components, animations and layout widgets that our design system leverages.
The added benefit of working with a separate component library like this, is that we can quickly iterate on a UI component in isolation. No need to setup test data or mock API calls, no need to re-navigate screens and get the component into a particular state. We can instead build out the component in a controlled sandbox, covering all states with automated golden tests, before incorporating it into the app. It's an approach popularised by tools like Storybook, and has proven to significantly improve our development cycle.
3. Automated & frequent releases, with Github actions
We strive to release often and iterate on features, to deliver business value early. Automated releases using Github actions allow us to do this.
We build multiple times per day, and use toggles to switch off work in progress, to ensure we are always in a release-ready state. This gives us the flexibility to choose a release cadence that balances new features to users, and not overwhelming them with app updates. Typically, this means a release every sprint (2 weeks cycle). Some releases are internal to our alpha users, which provide opportunity for feedback from stakeholders within amaysim, before a public release.
With Github actions, gone are the days of maintaining our own CI/CD stack, which was particularly painful for iOS. Builds are triggered when raising pull requests and again on merge to our main branch. For a release, a single git tag
command triggers a pipeline to lint, build, test and package up an app binary, which is published to TestFlight for iOS and Playstore for Android. From there we distribute our app to QA and stakeholders to test and explore app features, before promoting the release to public app stores.
The pipeline generates two flavours of our app - an alpha release and a store release. Our alpha builds include features that increase the testability of our app, so we can more easily do exploratory testing on real devices. For example, it includes an environment switcher, to allow our QA engineers to point at non-prod environments and to perform transactions with test credit cards. Our store builds differ only in that the don't have these test features.
4. Native app observability, with NewRelic
Understanding whether features are working as expected, are useful, and effective, is key to our approach to continuously improve the customer experience of our apps. When we release a new feature, we want to learn how it performs in the real wold.
As well as bringing new features to our app users, we want to make sure the level of service that existing features provide is appropriate. If theres a problem, we want to know about it before our customers, and avoid negative app reviews/ feedback.
To do this, we include tracking plans for every new feature, instrument our app with anonymous events, metrics, logs and traces, then use NewRelic dashboards and alerts to monitor our apps health, and measure how the feature is performing.
One of the challenges we've faced here is to avoid being overwhelmed with events and to make sure we focus on the key metrics. We've found the use of service indicators - a quantitate measure of behaviour, and service objectives - a target value for a service indicator, useful for this.
Keeping a close eye on the performance of our app allows us to detect (and fix) issues before escalation, allowing the team to focus more on delivery and avoid being distracted diagnosing and debugging issues.
5. Full stack, cloud native
App team developers are full stack engineers, in that they are capable of working on a feature within the native app codebase, writing Dart and using Flutter, as well as the Restful APIs that the apps consume, working with Node and serverless. We’re a small team of generalists, but with several of our engineers also specialising in iOS, Android and cloud respectively. This skillset is our key capability, allowing us to take (mostly) end-to-end ownership of features, with less handover and dependency between teams.
This shift to full stack engineering was made possible with our move from legacy monoliths, to cloud native microservices. Our microservice architecture allow us to develop, test, and deploy services independently. With cloud native microservices, we can leverage ready-to-use infrastructure.
There are of course many differences between a native app stack and serverless. However, we've found the similarities between Dart and Typescript, and being able to share a common IDE for instance, make context switching less of an issue.
Reflecting on the past few years of app engineering here at amaysim, these are what stood out to me as our most significant technology-based accelerators. There is much more to say on each of these, and comparable technologies to consider. We'll have more to say on that in future posts.
If you like the sound of this and are interested in being part of our apps engineering team, check our our Careers @ amaysim and our LinkedIn.
The views expressed on this blog post are mine alone and do not necessarily reflect the views of my employer, Optus Administration Pty Ltd.