Elvis Sun
6 Lessons learned from launching a Flutter + Firebase App

How a single developer team shipped an app within few months while working full time at Google.
Earlier this year, I was chatting with a friend I met while working at Google. Accidentally, we found out how we are both tired of getting the same gifts from different people and sharing messy emails and clunky messages and notes with friends about our birthday wishes.

My birthday gifts this year 😂
We decided to do something about it, and we created Antaa. We wanted Antaa to be a close-knit social network for sharing wishlists across all e-commerce platforms. Our initial vision is to just help our families share their wishlists and find the perfect gifts for the ones we love.
Although my friend was a PM at Google and gave me many great suggestions on product directions, I was the only developer writing any code. Naturally, we turned to Flutter + Firebase because of how fast you can build production-quality apps with them. We ended up shipping Antaa within a few months while both working full time at Google, thanks to the power of Flutter and Firebase.
Screenshots from Antaa
This is my first app development experience. As every developer would, I made tons of mistakes and I'd like to share some of them with you so you don't have to repeat them.
1. Choose solid architecture upfront
When you have a new idea you want to build, it's very easy to get too excited and rush to just get the first version implemented. But shipping a production-quality app is a marathon, not a sprint. A couple weeks later you'll regret not starting with solid architecture.
I started the app with the Provider package for its dependency injection and state management. It works great for the first month. But as we added more complex logic into the app, especially with the need of combining multiple Firestore listeners Streams, the codebase became increasingly hard to manage.
After some research and experimentation, I decided to use flutter_redux for state management. It allowed us to separate the widget logic from the business logic, which made our codebase much cleaner and testable. It also allowed us to make the app feel a lot smoother by leveraging caching and optimistic UI, which is much harder to implement without Redux for sure. For more tips on improving your app's user experience with the Redux pattern, check out this article by Alex Okrushko.
Another thing I regret not doing from the start is using built_value. After introducing redux, I wanted to cache the entire `store` to disk so it can be used to hydrate the `store` the next time users open the app. And doing so requires the entire `store` to be serializable. If I started with defining all my models and store with built_value, this would have been much easier than refactoring to use built_value after the realization.
In short, try to not get too excited about building something new and take some time to choose a solid architecture from the start. It'll save you lots of time in the long run.
I recommend checking out Brian Egan's flutter_architecture_samples repo to get started.
2. Minimal technical debt
Having a rule of minimal technical debt from the start is crucial. Whether it's writing unit tests, widget tests, refactoring, or simple things like setting up a linter.
Not having tests in place from the start will often result in writing your code in a non-testable way, adding them in after will take many times longer. So I recommend having solid unit test and widget tests setup from the start.
Flutter's widget tests are very fast, and it works great with flutter_redux. I ended up setting up my widget tests without having to mock out any part of redux at all. It allowed us to test some high-level behaviors end to end with few lines of code, almost like integration testing. It gave us the confidence we needed to get to the release frequency we wanted. But the best part is: it still runs blazingly fast! (300 tests under 22 seconds)
The same principle of minimal technical debt applies when you see a mistake - fix it early. I made the mistake of defining my Firestore schema in snake_case instead of camelCase. It's not a huge deal, but this made me write additional layers when interacting with Firestore. Initially, I realized this is painful but didn't think it was worth fixing. It would have taken a couple of hours at the time: rename everything, purge the database, and done. But I made the mistake of not fixing it - now I have to live with it. Now the app is in production, fixing this will take multiple weeks instead of hours. Don't be like me.
3. Stay current on the latest versions
The Flutter and Firebase team are constantly shipping new features to make our development experience better. You can easily leverage this by staying current on the latest versions.
For example, when I started, the firebase_auth plugin returns the currentUser asynchronously. This made it very hard to use because you either wrap everything in a FutureBuilder or have to unwrap and manage the currentUser in your own state management layer. Luckily in their recent release, they fixed this by making it synchronous. This would have saved anyone using the plugin hours - kudos to the FlutterFire team.
Use pub outdated and npm outdated to update your packages frequently so you get the benefit of all the latest features. Or better yet, have a bot do it for you. (The prerequisite for this is to have solid tests.)
4. Set up your CI/CD pipeline and deploy frequently
When building a new product, you should get it in front of your customers as you are building it to validate your product-market fit. Additionally, you want to tighten your feedback loop by shortening your release cycle as much as possible. The best way to do this is to set up your CI/CD pipeline.
We set up our CI/CD pipeline using Fastlane on Circle CI. They have a generous free tier and was very easy to set up for both Flutter and Firebase. Every time I submit a change, it runs tests for Flutter, Firebase functions, Firestore security rules, Flutter driver tests, uploads code coverage info to codecov. Then if all looks good, it'll distribute both the Dev and Prod apps (connected to different Firebase Projects) to my team via Firebase App Distribution for internal testing, Play Store and Test Flight for alpha testing or production release.
This saves me a couple of hours a day from babysitting releases and gives me time back on building new features. Let me know in the comment below if you'd like to hear more about how I set up the CI pipeline.
5. Security Rules cannot be an afterthought
There are many apps using Firebase that are running in the wild with no security rules set up at all. Meaning these apps have databases that are globally readable and writable!
Setting up proper security rules is essential to your users' privacy and your app's success, and it's important to do this from the start.
In Antaa, users can become friends with one another and only friends can view each other's wishlists. Therefore we need a rule that says "only give read access if a user is a friend with the author of the wish". However, when trying to implement this in code, I realized it was impossible with the initial Firestore structure I had. The friend document was stored as users/${user1Id}/friends/${randomId}, and this makes checking if two users are friends with Rules impossible because an indexed lookup is required. As a result, I keyed the friends documents as users/${user1Id}/friends/${user2Id}, which then allowed me to check friendship with the built-in exists() function.
Finding this hidden complexity early saved me from doing another time-consuming migration, which is why I recommend thinking about your Security Rules from the start.
6. Plan ahead for your releases
Plan at least 2 weeks for the App Store and the Play Store to review your releases. When we submitted our app to the App Store, the review was pretty thorough and they even found a small bug for us. The whole process took a few days. But the same app submitted to the Play Store took over two weeks for them to review.
With many people working from home, both the Play Store and the App Store have more apps to review than normal. Definitely plan ahead for your releases and take the potential changes you might be asked to make into consideration as well.
Conclusions
Thank you for reading and let me know in the comment below on what other mistakes you would have avoided if you were to write your app over.
Lastly, if you want to play with our app yourself, or share your holiday wishlist with your family and friends - you can give Antaa a try here.