Would you bet a four-month deadline on a framework that hasn't reached 1.0?
I did, in 2018. More than 40 screens, on iPhone and Android, in both stores inside four months.
EntrenaPro lets coaches and athletes book individual and group sessions across more than 40 sports in Spain. A product owner I'd worked with before asked whether the whole app could be rebuilt from its mockups, for both platforms, in under four months. Any cross-platform framework was on the table.
Why Flutter, and not React Native?
I'd seen Flutter's first demo in 2015 and never had a reason to use it. I had one week of React Native behind me, on a side project I never finished. It came down to four things:
- Speed of delivery. Everything I'd read said Flutter teams shipped faster.
- Every pixel is Flutter's. Flutter draws its own widgets on its own surface instead of borrowing the platform's, so a screen looks the same on both, whether or not the platform has a matching native widget. React Native's shadows had eaten most of my first week with it.
- Types in the language. Coming from Java and Swift, Dart's static types felt like home, and the IDE could actually help.
- A bias I admitted. I liked Google's tools, and I said so at the time.
The cost: a pre-1.0 framework and a much smaller package ecosystem. Most of the interesting work below came out of that gap.
What it took
- Productive from day one. Dart reads like Java. My RxJava habits mapped onto Streams, and JavaScript promises onto Futures and async/await. Login and registration shipped in the first week, and the designer's verdict on the screenshots was "very balanced".
- Hot reload changed the loop. Most UI changes showed up in about 0.7 seconds, two at most, with the screen's state intact. My estimate at the time: more than 40% less testing time than native.
-
Missing widgets became packages. The design needed a month picker and a two-thumb range slider,
and neither existed. I read the source of PageView and the Cupertino slider, built both, and published
them:
month_picker_stripandcupertino_range_slider. -
Stripe without an SDK. There was no official Dart support. Wrapping the native SDKs was the
obvious route, until I saw that Stripe's iOS and Android SDKs didn't agree on their own function signatures: one
returned a result, its counterpart returned nothing. So I wrote the integration in Dart, from Stripe's API
documentation, with the Android SDK as a reference. It became
stripe_api, still my most-starred repository. - Push notifications, twice. The official Firebase plugin worked until the requirements changed: notifications had to update the screen in real time, and open the right screen on tap. I replaced the plugin with my own Firebase code in Java and Swift, talking to Dart over platform channels.
- Architecture, mid-flight. I started on Redux and never liked one store for everything. After the BLoC talk at Google I/O 2018, I moved the app to BLoC partway through the project.
What shipped
- Two roles, athletes and coaches, each with its own menus, payments and features.
- Location-based search for coaches and athletes.
- Session requests, acceptances and cancellations, with real-time notifications.
- Stripe Connect for athletes paying coaches by card, and coach subscriptions through the Dart Stripe package.
- Session bonuses: packs of sessions a coach sells and ticks off one at a time.
- Ratings with comments and replies, a notebook for coaches to track their athletes, and profile sharing through Firebase Dynamic Links.
In both stores inside the four months. App Store review took one day.
What did the deadline cost?
- Strings, extracted from day one. I didn't. Near the deadline a second developer joined just to pull every string into localisation files and translate them. It took him a week, and every translation change after that was harder than it needed to be.
- Tests. The schedule was tight, and tests were the first thing cut.
Did the lesson stick?
The write-up ended with my "what's next" list: tests, then continuous integration and delivery, then isolates for heavy work off the main thread.
Eight years later, 39% of PRCHD's code is tests and its releases build in CI. libtailscale runs every blocking call on its own isolate.
The bigger lesson took longer to name: a tight deadline on two platforms is the best argument for one codebase. Every Flutter app on this site goes back to that bet.
The numbers
- More than 40 screens, iPhone and Android, about four months, 2018, on Flutter before 1.0.
-
Three packages published along the way:
stripe_api,month_picker_strip,cupertino_range_slider. - The write-up: "EntrenaPro — From Zero To Flutter", first published on Medium, 2 October 2018.