- Will Krause
Man leaves frontend project unattended for almost a year and is shocked to find it mostly still works.
I work on passion project called Lingamo, a mobile app for learning Spanish. It’s one of those projects that is characterized by periods intense active development, followed by long periods of inactivity. It exists today mainly as a way for me to explore new technology. I’ve built some form of this app in at least four frameworks now (EmberJS, React, React Native, Flutter).
Prior to this weekend, April 1st 2021 was my last commit to Lingamo. It felt mostly done for my needs and I was about to start the grind that is leetcode in search of a new job. I’ve left some version of this project unattended several times in the past, and different frameworks have handled backwards compatibility better than others. I don’t know what the current state of React Native upgrades is, but 3 years ago it was a nightmare to upgrade, even for an actively maintained project. You’d have to sort through unintelligible diffs of raw .pbxproj files and hope you resolved the conflicts in a way that would not outright (or worse subtly) break your app.
When I stopped work on Lingamo, Flutter’s use of Dart with null-safety was just starting to gain traction, where the majority of my dependencies were at least supporting them in a feature branch. My project did not use null-safety, and I had not started to migrate before stopping work on the project.
Coming back to it now, null-safety is really no longer optional. That’s a pretty significant change, that in other projects would have required days if not weeks of work to adapt the code to the new paradigm. This being a hobby project, I was able to throw caution to the wind and see how far I could get with just upgrading all of my dependencies at once.
flutter pub upgrade --major-versions
Now, normally this is a good way to waste a lot of your time, as typically several things will break at once in a way to is impossible to reason about. However, sometimes you get lucky, and of the 30 or so dependencies for this project, almost all of them just worked. I needed to replace an unmaintained package apple_sign_in with the_apple_sign_in. I had to make one small change to how I setup one of my charts using the charts_flutter package. The largest migration effort by far was going to flutter_bloc 8.0.0, because of the deprecation of mapEventToState, but that change was well documented and straight forward. No code using core Flutter libraries or widgets needed to be changed.
Of course, I needed to upgrade my own code. Again, the tooling here really shines. I use two native plugins that I don’t just take from pub.dev. One is a forked version of assets_audio_player, because I need more frequent track progress data, and the other is a wrapper I wrote around the translation libraries for ml-kit to do full sentence offline translation. Both were easily updated using dart migrate. Once those changes were made, I ran dart migrate on my main project and just accepted all of the recommended null-safety changes. There were a few places in my code where there was a type mismatch due to nullable vs non-nullable type declarations, but the debugger found those and they were easy to fix.
Man Yells at Clouds
Maybe this is more a statement on the state on frontend frameworks that having a project work after nearly a year of inactivity is so shocking. However, this natural experiment of mine is a good piece of anecdata related to the overall maintenance burden that Flutter imposes. The only major core change was the introduction of null-safety (an important feature that Dart and Flutter are better off for), and even then, the team provided excellent tooling to help with the migration. Compare that to React, where somewhere along the way the community decided that hooks were the pattern everyone needed to learn and adopt.
Coming up with good abstractions is hard. Sometimes it can feel like for the long-term health of a project, breaking changes are justified. Sometimes they are, but it's tempting to pursue a global maximum as if you were not constrained by prior choices. Sometimes verbalized as: "If only I could do it again with what I know now.". I think it is commendable that the Flutter team mostly resists those impulses, and when a breaking change is necessary, it is well documented and comes with supplemental tooling to help with the process.
If a guy with a couple hours over the weekend can upgrade a moderately complex Flutter project after a year of inactivity, you can probably trust Flutter to be a reasonably stable option for your mobile apps.