Flutter Holobooth - Going Deeper

As part of the lead up to Flutter Forward, Very Good Ventures and the Flutter team have released the Holobooth app, along with a blog post describing how it works, and the source code.

I intend to examine the source, and see what I can learn, and post my learnings here. After my first look post, this post will look a little deeper, and sometimes gets opinionated.

Going Deeper

Bloc and Provider

The app uses Bloc and Provider. I’ve used Provider before, and since switched to Riverpod; Bloc is new to me.

I’ve learnt that:

  • Bloc supports an observer, similar to Flutter navigator, but only one, while Navigator supports multiple.

I don’t recall seeing Blocs that directly use other Blocs, and the examples I’ve looked at all use only a single Bloc, leading me to believe Blocs are not composable like Riverpod providers are.


Completers are used in places, and are a Dart feature I’m yet to use, but should look into.

I’ve learnt that:

  • Completers can be used to simplify complex Futures, and to turn call-back based (async) code into a Future.

Lots of Small Widgets

Holobooth is composed of lots of small widgets, rather than fewer large, complex ones. This helps keep the app performant by limiting the scope and size of rebuilds. Each widget occupies its own file.

ErrorView Widgets

Where needed, a separate ErrorView widget is used, rather than a widget having the dual role of displaying data or an error message.

Barrel Files

Each package, and each folder in lib has a barrel file.

What’s a barrel file? Simply put, it’s a way of importing all the files in a folder with a single import statement, instead of one per file. I think you’d get the most value with a well thought out folder structure. With IDEs that automatically manage imports, I don’t see a lot of value in this.

Apparently it can also be used to hide/show package internals/externals (but the hidden file/class/etc can still be imported, so it’s imperfect).

I’ve learnt that:

  • Barrel files simplify/reduce import statements, but can be easily bypassed if used to hide implementation details.

Custom Exceptions

Liberal use of custom exceptions => packages are thought out and planned.

Is this preferable to Riverpod’s AsyncValue class? I think I prefer result types, though at package boundaries I’m happy with exceptions.

What I’d Do Differently

This is where I get opinionated - some of these will be related to style and personal taste.

Package Names

All of the package names end with Repository, even when the name indicates functionality that would better fit in the domain or application layer.

The app uses Navigator 1, and does not have a central location for defining routes, etc. Some packages provide a route definition.

This is fine, given the minimal navigation the app requires.


The only logging in the app is in AppBlocObserver, and is done via the Log class in the dart:developer package.

Leave a comment