Flutter - Zero to Production in 3 months - Building India's Biggest Trading Platform
Note: This is an excerpt of the talk I did at Fragments19 by HasGeek. Below is the video of the talk.
Hello everyone! We recently released a major update to our flagship app Kite. And it was built from scratch in Flutter by a team of two. It took us only 3 months to build a production ready app. This is my experience architecting and building Kite in Flutter.
At Zerodha we build platforms for trading in stock exchanges like NSE, BSE etc. And we do it really well, we are #1 in India in terms of number of active clients. Kite has more than 1 million active devices which places around 3 million orders per day. Kite shows changes in stock prices real time. Our backend sends approximately 30 million ticks every second to around 300 thousand live users. So Kite will get these updates for each stock at more than once a second. Updating these prices within milliseconds at the same time keeping the user experience is probably the biggest challenge in building it.
First version of Kite was built in native Android. It took around 1 year to finish it. There was no iOS version. Later we decided to go cross platform with React Native. We built it in 5 months. But we published it only for iOS. Because Android build was completely unusable. After like one year we saw Flutter and decided this is it. I personally felt like Flutter is so many things done right. I’ll explain why I felt so.
Let’s start with workflow. Flutter has the best tooling period.
Flutter CLI is the single place to manage everything related to your project. Flutter doctor command will help you fix all your config issues.
Flutter build command can build your app in any device, any configuration, any architecture. You don’t have to goto Android Studio or Xcode.
There are IDE extensions for VS Code and Android Studio. They are very helpful while writing UI code.
Probably the best feature in Flutter is hot reload and hot restart. Unlike react native’s HMR this one is consistent and predictable. Reload happens in less than a second and restart happens in less than 2 seconds regardless the size of the app.
Upgrading Flutter version is seem less. We started at 0.5.6 and now it is 1.2.1. It never broke our code. APIs were backward compatible. Our react native app is still on 0.49 and we are scared to update it because it is breaking many places and the errors are cryptic.
Another thing is easy UI testing. You don’t need any external tools to do automated UI tests. You can write those in pure dart just like you write unit tests.
Best of Architecture
Dart builds in two different modes JIT and AOT. JIT is used in debug mode and AOT is used in release mode. Dart’s AOT compilation uses tree shaking compiler which removes all the unused code. The output of AOT is a native binary which makes it impossible to decompile. So if you have some proprietary logic then this is useful.
Flutter allows two way communication with the platform. Method channels are used for calling native APIs from Flutter and Event channels are used to send events from platform to Flutter, sort of like BroadcastReciever.
Flutter has a super simple animation API. In Flutter renders are cheap. There is no diffing algorithm running after you call setState. So you can change literally anything in the UI and call setState and in next 16th millisecond it is updated. I said 16th millisecond because it is timed by vertical sync. Which means you can connect animations to gestures and built in physics and you will get 60FPS interactions.
Since Flutter gives an app that is self contained which means it does everything on its own without much help from the platform you can easily port it to other platforms. Thats where the Flutter Embedding API comes in. Flutter desktop embedding is a project that uses this mechanism and it allows to run your app in Linux, Windows and MacOS. A developer has ported it to Raspberry Pi which will be great to build Kiosks. Another project from Flutter team called Hummingbird ports it to Web. So this actually allows you to run Flutter apps in 8 different platforms from a single code base.
The thing I like most about Flutter is its customisability. You can learn and edit the source code of literally any widget in Flutter and create something new from it. Every single UI element is written in pure dart. And it the code is readable and heavily commented. We have gone crazy on this and made everything our designer asked for and more.
Here are some animations we did. The one on the left is a morphing between the summary card a search bar. Our designer made a video of this in AfterEffects and showed it to me and I was able to build it in an hour. The time for idea to creation is very short.
On the right we have a backdrop. We do a couple of things here. There is a Y axis translation of top layer. We animate shadow above the top layer. And we animate opacity of an overlay on top of top layer so that it looks dimmed. All of them are controlled by the same controller.
Here are some interactions we built. On on the left is a sortable list. There is a built in widget for this but that requires long press on the item to initiate drag. Here we moved that control to a handle so that you don’t have to spend 300 milliseconds holding the item. On the right side you can see a bottom sheet. Built in one in Flutter has a fixed height. You can’t change that. So I made a custom one that can be expanded like this. This is something I took a lot of time to fine tune. If you look closely you can see the scroll speed is different. It is because I wrote a custom scroll physics for that so that when people scroll it won’t shoot to the end of it.
There is a widget in Flutter called CustomPainter. You can use this to draw things like lines, curves and shapes in the foreground or in the background of other widgets. This is like HTML5 canvas or SurfaceView in Android and has similar APIs. On the left you can see the price field and some of the tags like SL and SLM has a fence on top of it. This is to show that they are disabled. This is done using CustomPainter and some maths. On the right side you can see a trend line with gradient and when you scroll on top of it horizontally you can see the value and the date at that point.
In Flutter it is very easy to apply themes. You can define all the colours and typography in a Theme and you can feed that to your app. It will be used by all widgets in Flutter. So if you create multiple themes and switch between them the entire app changes automagically. It is easy to do and you will get a bonus transition like the one you see on the right for free.
Flutter also gives very flexible layout system. It is based on flex box. But there are a lot of abstracted widgets on top of Flex that makes it easy to compose UI. For example Row widget is basically flex-direction: row, Center widget is alignItems: center and justifyContent: center, Expanded widget is flex: 1.
If you need a responsive layout that will work on both phones and tablets you can use data from MediaQuery. If you want to save your app from notches put it inside a SafeArea widget.
If you think Row and Column is not enough then you can use CustomMultiChildLayout widget to place your child widgets at pixel perfect locations.
If you think that is not enough and you want to change the way things render on screen you can extend RenderBox or RenderSliver widgets. A Box is a widget that doesn’t scroll and a Sliver is a widget that can scroll.
And by the way a Sliver widgets have on demand rendering built in to it. This is something available only to RecyclerView in Android. In Flutter anything that scrolls including ListView, ScrollView, TabBarView will not render the things that is not in the view port.
Flutter allows a two way communication between widgets in a tree. A parent widget can send data down to the tree using InheritedWidget. This is how ThemeData is transferred to Text widgets. Reverse communication is also possible. A scroll view can tell the app bar how much it has scrolled through something called Notification so that app bar can hide if scroll view has scrolled up.
So we decided to use Flutter. And I was in charge of architecting it. But we were one of the early adopters. Which means there is not much help from community. The problem is that Kite is a really huge app. It has almost 50+ screens and 240+ dart files. We needed a framework that will work with this load. And nothing was there at that time. So I built many things from scratch, including an HTTP framework, a state management system and code generation mechanism.
Custom HTTP Framework
There are many HTTP libraries for Flutter. But at that time none of them had all the things we needed, things like request timeout, caching, file uploads etc. And none of them had a good looking constructor like this. Look at that, it is gorgeous! I also added a nifty feature: JSON parsing. If the parsing fails it will report as a failure saying unexpected response.
Custom State Management
Just like HTTP framework I couldn’t find a really good state management library. So I built one. It is based on Redux. I combined both action and reducer into a single unit called a mutation. On the left you can see three mutations for an API call fetchFunds. fetchFunds mutations starts the API call and based on its result either success or fail mutation is called. And screens listen to these mutations. Whenever a mutation executes screens will get a callback to re render itself. Our Store is a flat model. And we persist the store in shared preferences after serialising it to JSON. We don’t use any databases because it simply is not necessary for our use case and we need fast access to our data.
Dart Streams for Ticks
For showing price changes that happens multiple times a second mutations are not a great idea. Because it re renders the whole screen which is useless. So we created dart streams for every stock and we push the changes to the stream. In UI we wrote a wrapper for StreamBuilder widget from Flutter. It will re render just the Text widget whenever there is change in price. This made the process very light.
Code generation is an important part of Kite’s architecture. Instead of writing boilerplate code we generate dart code from the meta data we write. Kids use it for writing JSON serialisers. We took it to a next level and used it almost everywhere we are lazy to write code. This is basically an abuse but it worked for us.
For generating models there is already a package called built value. But I didn’t like it for a number of reasons. So decided to write one. To show an example of what it does, you can look at this code snippet. On the left is the code we wrote and on the right is the code that is generated. It basically initialises all the fields from the value inside the JSON. It also supports nested models, enumerations, custom data types, default values, nullable values, lists, maps etc.
For mutations we generate the wrapper code to glue together mutations for API requests. In the code snippet on the left you can see the fetchFunds mutation from previous slide. On the right you can see the generated wrappers for them. The wrapper for fetchFunds binds fetchFundsSuccess and fetchFundsFail to the request object. These wrappers also notify the screens that it has executed and you should re render its widgets.
For screens we generate wrapper code for subscribing to different mutations. On the left you can see Funds screen is subscribing to all the mutations related to fetchFunds. On the right is the generated wrapper which does the actual subscription based on that array.
We also generated enumerations based on the same mechanism. Dart only has integer enumerations. But we have way too many things that has to be a string enumeration. In the code you can see on the top the code we wrote and at the bottom the code that was generated. In the generated code you can see for each item in the original class it creates a constant object of the class with the actual string value inside it. You can also iterate the enumeration though its keys, values and items. You can also compare them and concatenate them.
Logd was a byproduct of Kite. I really missed React Native debugger. And we wanted a very good logging mechanism to watch whats going on in the app. So I decided to build one for Flutter in ElectronJS. It shows a modified Chrome DevTools. A client library written in dart allows the communication between app and Logd server. It can log from external devices as well if they are in the same network.
It basically adds console log functions to your Flutter app. You can use them instead of the print function. It can log the mutations, the state before and after the mutation. You can use the dev tools’ filtering options with it.
It can also log your network calls. You can test with different network conditions and monitor timings.
Logd also exposes a persistent storage API which is based on the localStorage of the browser. So if you use this in debug mode you can actually watch what is getting stored in the shared preferences.
Flutter or no Flutter?
So should you Flutter or not? In my opinion if you have a very small team or you have to build a highly customised UI or you don’t have much time to develop the app or all of these then probably Flutter is a great choice.
If your app is currently using a JS framework like React Native and you need more performance then you should try Flutter.
But there are some quirks as well. There is no proper WebView support yet. There is a package currently in developer preview which will be enough for most use cases. In our case we created a native activity with a full screen WebView in it and control it from Flutter.
Also there is no proper multi ABI support yet. You can do split APKs but you can’t create one single APK or App Bundle with all the architectures in it. Flutter team is working on this.
And as I have mentioned earlier there is no reflection. So you will have to do code generation.
So in a nutshell Flutter is great for many use cases. Make sure you consider it for your next project or rewrite. You may not like it at first sight. Give it a second chance. Love at first sight is overrated.