How I Built a Production-ready Flutter Gaming Application that has 7500+ Daily Active Users (DAUs)

Dhruvam Sharma
Level Up Coding
Published in
8 min readSep 7, 2021

--

Flutter is an exceptionally well built technology if it is used in full extent, which might be true for each one out there. It transcends the boundaries of design language and expectations. It wields enormous power in the hands of the developer and helps construct pixel-perfect mobile app stories that build huge savings and generate income.

But with enormous control over the technology comes great responsibility

Because as developers, we have a lot of control, things can easily turn south. The product has to use a good architecture, and good tooling system to help provide a better User Experience to the customers. Flutter works the same way. Also, the technology is still rather new, different architectures are still in process. Although, Google has been promoting how to architect the app and how to use this technology, there will often come a time when you will have to go back to the basics of Android if you want to develop a fairly custom app.

So, we released 1.0.0 version of our gaming application last month, August 2021 and our numbers are only increasing. This product has been in the works for a long time but I was fairly confident of its outcome. And in this article, I am going to talk about what all it takes to build a Production Ready Flutter Gaming Product.

What did I build — SkillClash?

SkillClash is a skill-based real money gaming platform on which people can compete for real cash in tournaments and battles. There are multiple games to choose from and users can en-cash their winnings via popular options such as Paytm, Amazon, UPI, Bank Transfers or Mobile Recharges etc.

We have been developing a Mobile Application (Android, for now) for SkillClash for a long time and had been using Flutter. Here is a link to a discussion for deciding why we went with Flutter as a technology.

SkillClash is already live on the web and the mobile application is also live now. Our application is not available on the play store since Google doesn’t allow skill-based real money gaming applications. But here you can download our app and give it a try.

This image above is a showcase of a very small set of screens built by our designer, Shashank Kumar (in Airtel now)

The process of building

source

After I was provided with a huge set of screens to deliver, I opened my laptop and got back to Android Studio and started building just the User Interface, module by module, without any APIs and only mock data.

First few things to look at:

  1. Architecture I am going to follow
  2. State Management
  3. Navigation in the application
  4. Adding Games

Architecture of the application

Reso Coder Blogs

I knew the application was going to be enormous with more than 1,500,000 lines of code so I needed a good architecture setup that is hard to break, easily testable, and makes sure I use good patterns while writing code. When the platform is somewhat verbose errors and bad code quality is easy to seep in.

So I went with Uncle Bob’s Clean Architecture pattern.

What helped me wrap my head around the layered system of this architecture was Reso Coder Blogs. They were pretty easy to understand, easy to reproduce and follow. Although the boilerplate for integrating the first API in a module is a bit of an overhead but this doesn’t feel much when you get the hang of it.

A single module, for instance a module for Splash Screen and Login in the application, has a structure like this:

This same structure is followed by all the modules in the app. I call it a module because all the files related to Login are grouped together. Similarly another module like wallet has the same structure.

I decided to use 2 Flutter Application Modules in the project. One is the Main project, where the main.dart lies. And another one where I keep common functionalities, common User Interface and common utility classes.

State Management

State Management is a huge part of the whole game unless it’s a static application, of course. Ours wasn’t so had to go with some solutions.

I mixed two of them. Provider and Bloc. I used provider to help with a particular Screen’s state management and bloc to help with the communication of presentation layer with the data layer.

People out there starting fresh, or are unaware of the state management solutions, use this one if it supports your use-case. Because I knew the application was going to be gigantic so I had to use them both.

Points to notice:

  1. Smaller Widgets: Remember to create smaller widgets rather than creating widgets in functions in the same widget class. Breaking a bigger widget into smaller ones will help in performance optimisation.
  2. Provider Context: It’s easier to get an error that says that ‘Tried to listen to a value exposed with provider, from outside of the widget tree’. So remember to either use a builder widget under the provider widget or move the child widgets in a different stateless or stateful widget class.
  3. Set listen to false when setting value: Its very easy to fall in this trap. What happens is when you are accessing a provider state value inside a build function, the listen param is set to true by default.This hhelps in rebuilding the widget whenever a state value changes. But when we need to change a state value, we need to set the value to false so that the whole widget tree doesn’t keep rebuilding. Like this Provider.of<Counter>(providerContext, listen: false).add(n+1);
    Here is another explanation for the same.
  4. Use provider state deep in widget tree: Remember to use the provider values as deep in the widget tree. This is because wherever you consume the provider value in the widget tree, the whole widget rebuilds whenever there is a change. So split your widget wisely and use provider as deep as possible so that the rebuilding of widget is reduced to improve performance.

Navigation in the project

source

If you need to navigate to the same screen in many parts of your app, this approach can result in code duplication. The solution is to define a named route, and use the named route for navigation. Source

As stated on Flutter official Website, named routes for navigation can be pretty efficient in a decent sized application. Here is how to implement the same in a Flutter Project.

First, define a route generator class which is pretty basic. This class contains just a single method which helps you with generating routes and help in navigating. We what we need to do is just define the routes in the applications, which are just simple constant static Strings in a screen widget.

And if you want to pass in arguments, you can directly do that with the help of setting.arguments

class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {

// Language Selection Route
case LanguageSelectionRoute.routeName:
return _transitionRoute(
LanguageSelectionRoute(
args: settings.arguments,
),
);
...
}
}
}
// route_generator.dart

Once, this is done, you need to define a common _transitionRoute function that abstracts the calling-the-route code block in the same file.

static PageRoute _transitionRoute(Widget widget) {
return MaterialPageRoute(
builder: (_) => widget,
);
}
// route_generator.dart

Last thing for the setup is to assign an initial route which will open once the app is launched. And you also need to assign a function that is called when routes are passed on to the navigator which in turn calls our custom generateRoute function we just created in route_generator.dart

class MyApp extends StatelessWidget {
final analytics = FirebaseAnalytics();
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Dummy Application',
initialRoute: ReceiverSplashScreenRoute.routeName,
onGenerateRoute: RouteGenerator.generateRoute,
);
}
}
// main.dart

Now you can easily call any route with pushedNamed function on the Navigator.

Navigator.pushNamed(context, LanguageSelectionRoute.routeName);// dummy_widget.dart

This is how I used navigation inside the application. All the route handling in the same file and that keeps me sane that project will be cleaner and more manageable.

Adding Games

Tower Twist

Our product is a gaming application and I had to add HTML5 games into it. Although we already have a service to launch our games, I needed to present the games to the user in the most authentic and natural experience. For that I used a Trusted Web Activity.

So there is no package that already does that, so I had to basically create from scratch. Currently the application creates a native view and we call that view with the help of MethodChannel and I have also built a communication around that. But currently I am testing out the performance of a PlatformView. Although the documentation states that it is a costly operation and should be avoided until there is no flutter equivalent present. But I want to compare the performance and see which one stands better in terms of consistency and lag issues.

I have already written an article explaining how I incorporated Trusted Web Activities in our application. Check it out here.

That’s it for now. I hope you liked the project. I am always up for better coding practices and strategies. Since this project was developed by a single person (of-course along with a tester), the project is bound to have some issues. And I am always working on them as and when reported. We have hired a new person on the team this last month and hope to see faster and efficient performance.

For the next article, we will be discussing

  1. Text Management in application
  2. Upcoming changes to the architecture
  3. What’s more to come

--

--

Google-certified Android Developer @Unikon. Android Geek. In love with Flutter. Blockchain Explorer. Dancer. 🕺 Reader. Coffee Addict. 😍