Improving User Experience through simple OTA updates in Flutter

Search for a command to run...

No comments yet. Be the first to comment.
Here we will cover various aspects of unit, widget and integration testing in Flutter
In a previous post, we discussed using find.byPredicate for more accurate and bulletproof widget testing. Flutter: Up your testing game In this post we are going to quickly go through that same logic, but for data related unit testing. For the 95% of...
The Problem Wasn’t Triage Most AI workflows in software engineering still keep the human directly in the middle of triage. The AI might help write code. It might explain a stack trace. It might summar

There’s been a steady undercurrent of people switching tools lately. Copilot to Claude. Claude to something else. Codex quietly entering more workflows. A lot of confidence in different directions, of

It’s been said to me that at times my responses can come across as a “no” first-unintentionally, of course, but real all the same. Not long ago, someone on my team shared an idea they were exploring. I began with a caution, meaning to help them avoid...

The opening sentence of any message often carries more weight than everything that follows. I’ve learned this the hard way. Recently, a report shared something he wanted to pursue. I was supportive — I really was — but my first response came as a cau...

One of the things I’ve always been comfortable with is admitting when I don’t know something. To me, it’s one of the real distinctions between the more and less experienced engineers — and honestly, people in general. When you’re earlier in your care...

Background
When I started building out the new mobile app for Wyztalk there were a few things on my personal ToDo list that obviously had great benefit to the business, one was a CI pipeline (Article) which I recently completed, this one, however, was something I got right quite early on.
With our current Ionic v1 app we needed to roll out a complete app deployment for CSS or Localization updates. While my implementations for both the UI and locale supported simply being hosted, even being small files, not something you want to be sending down the wire unnecessarily.
For the Flutter app, I spent quite a bit of time working on a simple way to implement this, for the localization we are using easy_localization as it was one of the simpler implementations that both supported JSON as well as online locale, something very useful for initial development.
For the UI (CSS) I also opted for a JSON solution as it allowed me more control over specific elements and edge cases which come up more often in a white label product and ended up being beneficial for this problem as well.
Solution
Both solutions are quite similar, but the premise behind it is that at build time we ship the current version of the Styling and Locale files, each of these files also includes a last updated date.
When the user first launches the app we copy these assets from the asset bundle over to the document storage.
In the main.dart file we have the following methods being called
*// Copy bundle assets into Documents directory
*await LocaleHelpers.*copyLocaleFiles*();
await ThemeHelpers.*copyTheme*();
then a bit further down we have these 2 to trigger the updates
*// Background update of Locale
*unawaited(LocaleHelpers.*updateLocale*());
// Check for theme updates
unawaited(ThemeHelpers.updateTheme());
The key difference between the locale and style copy methods is with the supported locale which can be defined with a build-time config.
static Future<void> *copyLocaleFiles*() async {
String path = await FileHelpers.*verifyOrCreateDirectory*(*_directory*);
for (final locale in globals.supportedLocale) {
String localeKey = locale.toString();
String localePath = '$path$localeKey.json';
// Check if the file already exists, if not copy it over
if (!FileHelpers.verifyFileExists(localePath)) {
String assetPath = "assets/locale/$localeKey.json";
String data = await rootBundle.loadString(assetPath);
await FileHelpers.writeStringToFile(localePath, data);
}
}
}
For updating the theme we first do a small check with the API by sending the last updated date from our current file to check if an update is available.
static Future<void> *updateTheme*() async {
String path = await FileHelpers.*verifyOrCreateDirectory*(*_folderName*);
var url = ApiEndpoint.uri('/theme').toString();
try {
bool shouldUpdate = await _checkIfUpdated();
if (shouldUpdate) {
var response = await Session.apiGet(url);
String localePath = '$path/$_fileName';
await FileHelpers.writeStringToFile(
localePath,
json.encode(response)
);
}
} catch (error) {
debugPrint('updateTheme - Unable to update theme');
}
}
The locale function follows the same idea but includes the loop as shown in the copyLocale to check each locale individually for an update.
While not a massive saving in the grand scheme of things, this does remove the need to host such sparsely updated resources or update a 10mb app for ~7kb of styles or locale.
Conclusion
I hope you have found some use out of this, in the end, we all need to strive to give our users the best experience we possibly can and saving bandwidth helps with that, also limiting unnecessary updates is definitely a win.
I hope you found this interesting, and if you have any questions, comments, or improvements, feel free to drop a comment. Enjoy your Flutter development journey :D
If you liked it, a like would be awesome, and if you really liked it, a cup of coffee would be great.
Thanks for reading.