My Profile Photo

7om Tech


Crafting top-notch mobile experiences with over 10 years in Android and now in Flutter. Quality and innovation at the heart of every app journey.


All-in on Flutter

It’s official, our talk was declined for Fluttercon 2025. Despite the frustration, I really wanted to share with the Flutter community a crazy project I’ve been working on. At first, I thought about making a video, then I remembered I have a blog I haven’t posted on in over a year. So, I grabbed my best pencil and started writing down the journey.

Let me tell you the incredible story of Project Codename: Mowgli.

Preambule

Once upon a time, a young and adventurous Android developer living in Paris decided to go on a world tour with his girlfriend.

We took our first flight on June 2022, and flash forward nine months later, we were in Kuala Lumpur, Malaysia. That’s when my existential crisis started:

What do I want to do?
Where do I want to live?

The second question was easy, we really wanted to move to Bordeaux to escape the crazy life of Paris.
But the first one… that was tougher.

Did I want to become a doctor, raise sheep in the Pyrenees, or become a fucking awesome blacksmith forging magical swords and hunting deadly monsters?

Hard Choice

None of those felt right. After 15 years of building Android apps, I came to a surprising realization: I had never released an app on the App Store.

I really, really (I’d add another really) didn’t want to learn Swift, and even less did I want to work with Xcode.
Then an idea popped up: Hey, it’s been a while since you checked out Flutter.

Within two weeks, I had built and released Factify on both the Play Store and the App Store, complete with ads and premium subscriptions. Honestly, it took more time to create my developer accounts and get the app validated than to actually build it.

That release gave me a sense of satisfaction I hadn’t felt in a long time.

Destiny is sometimes funny

A week after releasing my app, a friend of mine (an ex-colleague and biker), reached out to catch up. During our chat, he told me he was about to work on a Flutter app. The subject was confidential, but the real pain point was that the company wasn’t in Paris but in… (let me build some suspense)… Bordeaux!

So literally, a project was starting in September, on a technology I liked, in the exact city where I wanted to live.

My interview was scheduled for May, and I literally did it in a small hotel room in South Korea. The only info I could get was that the client, Betclic, wanted to build an “app-like game.”

A week later, I got the news: I’d be starting on the project in September.

In my spare time, after sightseeing during the day, I decided to dive deeper into Flutter. I started learning Flame and even created a particle animations library to better understand how things worked. That’s how Newton was born.

(And as of today, I still have a few features I’d love to add to this package.)

The beginning

On June 2023, it was time to go back home. I had mixed feelings, something was ending, but we were happy to see our family and start a new life.

After spending some time with our close ones, it was time to move to Bordeaux. We managed to find a flat quite quickly and, on September 4th, it was my first day. I signed the NDA and finally got to know a bit more about the project.

Betclic wanted to build a Poker app from the ground up in one year.
Yes, you read that correctly: one year to build an app (front and back) available on iOS, Android, macOS, and Windows.

In my head, I thought: Crazy… but ok, let’s fucking do it.

The project had already begun exploring what technologies we were going to use and what was doable with Flutter. But in September, we had the real kickstart. We were working in a separate building to preserve the NDA, and only a few people at Betclic even knew what was going on.

For the front-end team, two guys were already there: “Flute de Paon” (“Peacock Flute”) and “Pipo” (I’ll use Flute names to preserve the anonymity of my colleagues).

The First Steps

Our very first feature was… the login.
We could log into the app and land in the lobby, where, eventually, the poker offer would be developed.

Then we started implementing the game itself. We decided not to use Flame since it wasn’t really a video game, but instead went for a Flutter Casual Game Toolkit.

The communication with the game server relied on a websocket, so everything was bidirectional: we could receive events but also send commands to the server.

We started by implementing the first type of game, now called Spin & Rush.
It’s a short poker format where three players compete for the prize pool.

In the very first version, there was no prize pool, no lobby, the only action you could do was check.

Then we added call, raise, resolution, and so on.

From app to Framework + Desktop

In October, while we were actively working on the core game, a big question came up:
How will the app be released on mobile?

At first, we thought it would be a standalone app.
But we quickly realized it could actually end up being a framework embedded in the iOS and Android native apps.

This had a huge impact. Some features, like login or account management, aren’t necessary on mobile since they’re already handled by the native apps, but they are still required on Desktop.

So, in a single day, we went from a single-app project to a multi-module project with two apps. The framework and the Desktop app, both packaging only the features they need.

We set up Melos and followed this architecture:

Project Architecture

The goal was to limit interdependencies between modules and ensure we ended up with an acyclic graph of modules when packaging the apps.

Our main layers were structured as follows:

  • Utilities: All the non–Betclic-specific code. The rule of thumb was simple: if we could open-source it, it belonged here
  • Foundations: Core, non-UI components, mainly responsible for interacting with the Betclic platform
  • Features: As the name suggests, this layer contained the actual app features
  • DSM: The design system module, holding all the design tokens and assets shared across features. This is also where we defined reusable UI components
  • Composition: The layer where we started assembling pieces of the application, mainly handling routing and theming
  • Apps: The final layer, adding the last touches to routing and handling app-level configuration

After this decision we got back on track. In November Flutakouliss arrived. In December Santa Claus brought us the Y. The A-team was complete.

One table to rule them all

One of the main challenges when developing a mobile/desktop app is ensuring a single design works seamlessly across all devices. This was especially critical for our Poker Table experience.

To achieve this, we needed a reference screen. For us, it was the iPhone 14.
Our designer created the full table design, opponents, hero (your avatar and actions), board, etc. Once we had that, we turned it into a Table Specification, where every component was placed relative to the table itself.

When rendering the Poker Table on any mobile screen, we first compute the maximum available space (removing insets, navigation bar, etc.). From there, we calculate the largest box that preserves the table’s aspect ratio. Once that box is defined, it’s simply a matter of scaling.

At runtime, we determine the table size by multiplying the scale factor with the Specification, and we apply the same principle for relative positions.

In the end, we created three specifications:

  • Mobile Portrait
  • Mobile Landscape
  • Desktop

Each specification supports three layouts: 3-max, 6-max, and 9-max.

Adding other layouts is absolutely possible, we just need to extract the specifications from Figma, but these nine configurations have been enough for us.

As of today, we don’t have a way to fully automate this process, even though it’s technically doable.

iPhone 13 mini

iPhone 13 mini

iPhone 14

iPhone 14

iPhone 16

iPhone 16 Pro Max

Not ready

As the start-of-August deadline was approaching, reality hit us hard: we weren’t ready. End of story.

Fortunately, we got some extra time and the deadline was pushed to September. The final go/no-go decision, however, was set for December.

By then, the Poker Table had to be fully animated, and we also needed to support Omaha, a poker variant with four cards in hand and its own specific rules.

To be able to meet the deadline the A-Team was completed with “Picolo” in June and “Flutella” in October.

From FlutterAnimate to Rive

Animating widgets in Flutter is really easy, but it can also become really verbose.

Our first animations were done using flutter_animate. We even had a dedicated file storing all the animations for table elements (opponent cards, chips, etc.). That file was… well, let’s just say it was HUGE, thousands of lines, hard to maintain, and the animations didn’t really have that “wow” effect.

In September, we made a bold decision: migrate all our animations to Rive.
Cards, capsules, chips, most of our table elements became Rive components.

At the time, Rive was still really new (version 0.12), but it showed a lot of promise. With Rive, motion designers can create the animations they want and provide a state machine that developers can interact with.

For example, the capsule containing a player’s name has a state machine that lets us define its state: waiting, playing, or folded. All we need to do as developers is set the right state, and Rive takes care of the rest.

The magic is that we can update not just text at runtime, but also images. Even our cards became Rive objects, resulting in some really cool animations (see video below).

But great animations weren’t the only requirement: they also had to be controlled by the game server.
Since we’re building a real-time game, a user with a bad connection might otherwise end up with less time to play their animation.

We solved this with a simple approach: the game server tells us how much time we have to play an animation.

Take the reveal of the Flop as an example:

  • The reveal normally takes 500ms.
  • The game server sends us the end timestamp when the animation should finish (including extra latency buffer).
  • When the Flutter app receives the event, it computes how much time is left before the deadline.

If the time remaining is less than 50% (250ms), we skip directly to the end. Otherwise, we simply speed up the animation.

We ended up implementing a tailored animation system where a widget can receive animation events for a given target.

For example:

  • Our KoCenterWidget, which displays the big KO animation when eliminating a player in a PKO tournament, listens for animations on KoTarget.
  • When it receives an animation event, it computes a ratio that defines the animation speed.

The pseudo-code is as simple as:

@override
Widget build() {
  return Rive.widget(
    speed: speed,
  )
}

@override
Future<void> playAnimation(Animation<KoCenterAnimationType, KoCenterAnimationTarget> animation) async {
  setState(() {
    _viewModel // This a viewmodel from Rive using databinding
      ..colorLevel = animation.type.bountyLevel.toDouble()
      ..triggerStart();
  });
}

Our animation system is responsible to compute and fire animation events with the right speed. The speed is applied directly to the rive Animation. As you can see the result is really cool:

Release Hell

The release date was finally set for December 9th, 2024, right in the middle of my holidays.
I promise, it wasn’t planned.

That morning, after a scheduled maintenance, we flipped the switch and Mowgli went live.

It didn’t take long before our servers went down.
After a few days of intense work, the platform was finally stable.

On the front-end side, we had some bugs, but nothing major. What we really wanted was feedback.

The poker community delivered, they were genuinely happy, loved the product, and were super excited, as you can see in this image:

Gentle Feedback

Well… that was in my dreams and not worth to translate.

In reality, we got a lot of complaints.
No dark mode, no sounds, the app was crap. The poker community hit us hard.

It wasn’t the first time I’d released an app with major design and behavior changes. At Zenly, we did it twice. Both times people were disappointed and angry, but both times the number of users exploded right after.

When reading these kinds of comments, you shouldn’t take them personally. Instead, you need to understand what’s really causing the anger.

In our case, people weren’t happy about losing some features, but the feedback that came up over and over again was:

  • Dark theme
  • Sounds
  • Buggy animations
  • Ability to choose the deck color

In less than two weeks, all of these features/bugs were shipped.

That’s the power of mastering your code.

Finally some love

After a few weeks, we started to see more and more positive reviews popping up.
As expected, our user base grew, and grew a lot!

Month after month, we kept beating our own records:

  • number of users
  • number of games
  • and more.

We even started to take some market share from our main competitors, who had been in the poker field much longer than us.

At Betclic, we really care about what our users think of our products. We even set up a Discord with our most active players, where they can report bugs and share feedback.

As a developer, it’s always fascinating to see what users like, what they don’t, and what they’d love to have.

And now?

We keep adding really cool features that unfortunately I can’t talk about for now.
I have so many topics I could dive into, from our move from Rive to Rive Native, to multi-window support, performance tricks, weird crashes, or even how we managed near-monotonic time for animations. I may write dedicated articles on those subjects.

In the meantime, I wanted to share my feedback on working with Flutter.

First, I’d like to thank the Flutter community. It’s rare to find such a kind and motivated group of people. Everyone I interacted with was eager to help. The community is also incredibly active: whenever you think “I’d love to do this, maybe there’s a package for it”, there almost always is!

My team was also amazing, deeply dedicated to creating the best poker app and experience. Flutter itself delivered as well: we managed to maintain a single codebase for two apps and a framework. There’s still room for improvement, but honestly, as of today no other framework, not even KMP, delivers at Flutter’s level.

Big up to the Rive team. We had the chance to work closely with them, and their passion really shines through. Rive rocks, and I highly recommend giving it a try on your projects.

Of course, there are some frustrations too. The Flutter build system can be limiting, I miss Gradle from Android, especially for handling module variants. We worked around it with scripting (e.g. stripping out mobile-only assets when building desktop and vice-versa), but native support would be great.

Coming from the Android world, I was used to one big release a year. With Flutter, multiple releases per year are great, but the first release of a cycle often introduces new bugs. Usually, you need to wait for one or two patches for stability. Luckily, we never hit a dead end: rollback was always an option, and Flutter being open source makes a huge difference. You can even build your own version of Flutter if necessary, something I could never do with Android, where some bugs had no fix other than a painful workaround.

From our perspective, Windows support still lags behind macOS in quality. Considering Windows is (unfortunately) the most widespread OS, I’d love to see more effort from the Flutter team or Windows experts to improve this area.

All in all, after 15 years in tech, seeing the rise and fall of countless technologies, I can tell you this: there is no silver bullet. Every approach has its pros and cons, the key is making sure the upsides outweigh the downsides.

In our case, Flutter absolutely did.

I’ll dedicate more time to being involved in the Flutter community.

From the deep blue of my heart, I hope you enjoyed this article as much as I enjoyed writing it.

Flutterly yours,
Traversiere

comments powered by Disqus