Reusable Blocs

As a general rule, when you are programming something more than twice to do about the same thing it is probably a good idea to find a way to abstract it. Blocs are no different. You may have a single instance of each type of bloc you define in your code, but in some cases it may be useful to have a bloc for each of several similar elements on the page that do roughly the same thing. Or you might have a simple logic requirement that is repeated throughout your app.

In my latest app I found myself wanting a simple bloc that just controls a single bool stream. A checkbox, for example, just needs a single stream of whether it is checked or not, and a way to control the checking and un-checking of the box. While there may be ways to integrate the stream into an existing bloc (this would be particularly simple you are defining your own bloc classes), the Bloc package is not really designed to juggle several different types of output in one class. So in this case it makes sense to define a separate, simple bloc that just tracks the checkbox’s boolean value.

This Bloc is different from other blocs in that it is not instantiated directly anywhere. This is because the method to look up the bloc, BlocProvider.of<SomeType>(context), relies on the specific type of the bloc it is looking up. To make this work properly, and to help with readability, you can simply create an empty class that extends BoolBloc that will be used as a single instance.

The BoolBloc:

import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class BoolBloc extends Bloc<bool, bool> {

  bool value = false;

  BoolBloc() : super(false);
  BoolBloc.value(bool initState) : super(initState);

  void enable() => add(true);
  void disable() => add(false);
  void toggle() => add(!value);

  @override
  Stream<bool> mapEventToState(bool event) async* {
    value = event;
    yield event;
  }
}

class BoolBlocToggler<T extends BoolBloc> extends StatelessWidget {

  final Function(bool) childFct;

  BoolBlocToggler({this.childFct});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<T, bool>(
      builder: (context, val) => InkWell(
          child: childFct(val),
          onTap: BlocProvider.of<T>(context).toggle),
    );
  }
}

The Bloc itself is about as simple as a bloc can be. The helper widget, the BoolBlocToggler, acts as an inkwell that connects to and toggles a particular BoolBloc type. One use of it looks like this:

// bool bloc for showing / hiding delete icons

class ToggleEditList extends BoolBloc {}

   ...

   // in the page's multi-bloc provider ...
          BlocProvider<ToggleEditList>(
            create: (context) => ToggleEditList(),
          ),

     ...

// The BoolBlocToggler toggles whether the list is in 'edit' mode
class ItemList extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ToggleEditList, bool>(
      builder: (context, editList) { 
        ...
        BoolBlocToggler<ToggleEditList>(
          childFct: (val) => Icon(
            Icons.edit,
            color: val ? Colors.blue.shade200 : Colors.black87,
          ),
        )

     ...

// this bloc can be listened to somewhere else to respond to state changes
BlocBuilder<ToggleEditList, bool>(
        builder: (context, editList) {
           ...

If you need some dynamic behavior around similarly structured data in different parts of your app, you can definitely just build a bloc for each component and give each bloc its own name and file. While you are just getting started with Bloc, this is probably the better way to go as it reinforces and allows you to refine those patterns. But as your comfort with Bloc increases consider building a bloc which can be re-used and extended for multiple generic cases.

Implementing the Bloc Pattern with Bloc & Flutter_Bloc Packages

So far I have been exploring the Bloc pattern without using the packages Bloc or Flutter_Bloc, but just by designating certain classes as logic components and transferring data into and out of them with StreamControllers. The main advantage of doing it this way for me was flexibility, as the logic component can look like anything. It can have any number of inputs or outputs and connect to other blocs in any which way. Of course, this flexibility is not necessarily a good thing, and the final product ends up being rather convoluted, not particularly reusable or easily testable, etc etc. All the reasons why bloc is useful are out the window and all that is left is that we have sort of separated the logic from the UI.

It’s not that implementing Bloc is just using a particular library. As I understand it, Bloc describes a layered, stream-based architecture where logic components, which depend on a separate data layer and are depended on by UI elements, respond to UI events and emit states according to those events. The UI listens to those states and rebuilds accordingly. You can do that without using these libraries. Now that I have tried this, though, I see clear advantages of using the Bloc and Flutter_Bloc library. It reduces boilerplate code, sure, but it also reduces your ability to put code in there that probably shouldn’t be there in the first place. The narrowness of what a Bloc can do forces you to think about and separate components into individual pieces. Clearly defined States and Events make it easy to read, understand, and test.

Bloc

To make a Bloc with the Bloc library, which does not depend on the flutter framework necessarily, we import the library and make a class that extends Bloc<SomeEvent, SomeState>, where SomeState and SomeEvent are classes (that can contain some data about the state) or an enum. From here it is deceptively simple: the first override, initialState, sets the Bloc’s initial state, and the second, mapEventToState, takes SomeEvent and returns SomeState.

And that is pretty much the extent of the framework Bloc provides for the logic layer. The power of this simplicity is that it allows for a flat, easy-to-read decision-making structure for any given screen in the app.

Flutter_Bloc

The package Flutter_Bloc provides widgets to facilitate the connection between the logic layer and the ui layer. One of these is BlocProvider, which is like Provider but it handles overhead for the Bloc like creating and disposing things when necessary. BlocProvider seems to be the standard way for not just providing, but creating Bloc instances. And there is BlocBuilder, which mimics StreamBuilder, but listens to a particular Bloc. We can type it like BlocBuilder<SomeBloc, SomeState> and it will automatically look up SomeBloc that is provided in this context, and know it is going to emit SomeState for us to listen to. And there is BlocListener, which listens to a bloc and can respond with some action, like navigating to a new page or showing a message.

The basic components are relatively simple, the complicated part is maybe getting used to thinking about and creating components this way, and how to logically arrange these components so they are readable, reusable, and testable.

And so this is what I will try to do for the next project, build something similar to what we already built, with users and groups and shared data sets. I will try to keep updating as I learn more about using these libraries and how to organize and construct an application with them.

Saving and Validating Fields in the Bloc

One slightly messy consequence of the Bloc pattern as I have been using it is that some of the values that are being passed back and forth from the UI to the Bloc in streams are also kept as a variable in the bloc. So, the UI is getting the value by listening to the stream, but the value passing through the stream is also being copied in case it needs to be referenced later, in the bloc itself.

One example of when a value is ‘double stored’ like this is when a user is entering the details of a new event, like a bill. All of the fields for the bill details have their own StreamController (or BehaviorSubject) in the Bloc class. But when a value passes through that stream, it is also intercepted and stored as a variable so that when it comes time to submit the event, we can just reference a variable instead of trying to fish a value out of a stream that may or may not contain a value.

This is what the UI looks like where the value is entered:

StreamBuilder<double>(
  stream: newEventBloc.billAmount,
  builder: (context, snapshot) {
    return Container(
      width: 100.0,
      child: TextField(
        onChanged: newEventBloc.newBillAmount,
        keyboardType: TextInputType.number,
      ),
    );
  },
),

In other words, we just wrap the TextField in a StreamBuilder, specify the stream, and point the ‘onChanged’ property of the TextField to the stream’s public getter method.

In the Bloc, The pattern goes generally like this: first, declare and instantiate the StreamController as a private variable. Second, there is a public getter method which exposes the controller’s stream. Finally, there is a method or collection of methods for adding values to the sink of the controller.

BehaviorSubject<double> _billAmountController = BehaviorSubject<double>();

Stream<double> get billAmount => _billAmountController.stream;

void newBillAmount(String amount) {
    _billAmount = double.tryParse(amount) ?? 0;
    _checkIfBillIsValid();
    _billAmountController.sink.add(_billAmount);
}

In this case, the method _checkIfBillIsValid is called each time a new value passes through the stream. Specifically, it is called as soon as the user enters or deletes any character in the textfield. It triggers a separate stream that determines whether the ‘submit’ button at the bottom of the dialog is active, or whether it can be clicked. Also, the tryParse ?? syntax is useful because if the string is empty, the 0 value for the bill ensures the _checkIfBillIsValid method finds the bill invalid.

When I first wrote this code, it was slightly different and looked something like this:

BehaviorSubject<double> _billAmountController = BehaviorSubject<double>();

Stream<double> get billAmount => _billAmountController.stream.map(_saveBillAmount);

void newBillAmount(String amount) => _billAmountController.sink.add(double.tryParse(amount) ?? 0);

double _saveBillAmount(double amount){
    _billAmount = amount;
    _checkIfBillIsValid();
    return amount;
}

The difference here is that saving the value and checking if it’s valid happens as the value is passing out of the stream. The weird bug that came up had to do with the fact that I was using BehaviorSubject, which re-emits its last value each time it is subscribed to. This can happen if the UI is rebuilt, for example, if the keyboard appears or disappears.

BehaviorSubject and other rxDart StreamController abstractions are useful because StreamController really only can handle one thing: controlling a single stream and passing it on to a single listener. Imagine a person who completely breaks down if more than one person is listening to them. That is a StreamController. The problem is that there are plenty of instances when we want to listen multiple times, maybe even from the same widget that is rebuilt from navigating around the app. In this case we can either use StreamController.broadcast() or just go ahead and import the rxDart dependency and use one of the useful StreamController-like things there. These are abstractions around the StreamController, so the StreamController is still there, but it is only being listened to by the Behavior/PublishSubject, which itself is more socially comfortable and can pass the data on to any number of listeners.

Back to the weird bug – the rebuilding of the UI was causing the stream to be re-listened to by the StreamBuilder. Since BehaviorSubject returns either a seeded value or the previous value to pass through the stream, if there is one, when a listener subscribes, the method _saveBillAmount was being called unnecessarily. In some places in the app this actually led to buggy behavior, but other places it was difficult to notice if anything unusual was happening.

The solution was quite simple – either use PublishSubject, which is more like a StreamController in that it doesn’t emit anything just for being listened to, or move the methods that were causing the buggy behavior (save the amount, check if it’s valid) to the front of the stream so that they don’t get triggered by the stream re-emitting the last value, like in the first example. So, if the UI needs to rebuild for some reason out of our control, the stream can be re-listened to and no weird side effects pop up.

Light at the end of the tunnel

It is a bit of an elating feeling when you start to feel the warm glow of the light at the end of the tunnel. The tunnel, in this case, is a long, maybe disjointed or chaotic development process. But the features and functionalities start to layer on top of each other, and eventually the product starts to feel like something that could be useful. The remaining features it needs become clarified. If only it had this, and that … it might actually stand up on its own.

I have also started looking at competing apps, and seeing what features they are able to include, sometimes with teams of engineers and millions in publicly announced venture capital investment. It is both humbling and vindicating. Some features are gimmicky but make it feel more professional, more designed. Some are gimmicky and get in the way. Some features are genuinely useful and cool. Those deserve to be considered, and maybe incorporated or built on in some way.

But, somewhat surprisingly, none of the apps I have seen yet are able to split and prorate bills according to the date of the expense. For example, this could be useful if a roommate moves in or out at some arbitrary point through the duration of the water bill. Some apps allow you to manually calculate and enter each person’s responsibilities on a per-bill basis. But none do it automatically, or base those modifications directly on the category of the bill. None even differentiate between the date the bill is paid and the dates the bill is for. In fact, none I have seen allow you to specify a range of dates for a certain bill, which in my experience of looking at and paying bills, is a super relevant feature of just about every single bill you receive as a household.

So, we have identified our niche. The next steps are to finish the features that it needs to be viable, create a website and some sort of public presence, articulate how and why this will be useful and unique in our public messaging, and work on the initial user-experience, which is an easy way to lose a lot of your users if it’s at all confusing or particularly unattractive.

Upgrading to Provider, reflections on Flutter

There is always tension between jumping into something or taking time to map out all possible routes before committing. Jump in too early, and you might jump in the wrong direction. It is always a mixture of some intuition, a bit of healthy skepticism, and a hefty dose of blind faith. Sometimes all you have to go on is something a friend said to you at a party, like “there’s this new cross platform framework written in dart that is all open-source and compiles to ARM code, it seems pretty cool” but you look up to that friend, and that comment leads you on a long journey of commitment, jeopardy, and discovery.

Sometimes, you make the wrong choice. In the case of Provider, I made the choice to ignore it because I thought it was some alternative to using Bloc, and I didn’t want to change directions before I really understood what I set out to learn. This was a mistake, because I understand now that Provider is the natural pair to using Bloc. It is the glue that holds the first two layers together.

Fortunately, often these competing directions are not completely orthogonal. In the case of BlocProvider and Provider, they are actually very similar approaches, so it is no big deal to switch to using Provider. Similarly, if the entire Flutter project were to somehow disappear tomorrow, I look back at my Flutter journey and marvel at how I have progressed as a developer and designer of apps and databases. It might be an adjustment to go back to android or learn native ios development, but that sounds much more doable given what I have learned in Flutter, especially with reactive programming, designing and working in cloud firestore, and general app architecture.

The only difference between how to use Provider and BlocProvider is that you need to manually give Provider a ‘dispose’ method when you declare it, and call the Bloc’s dispose method there. Although this is one extra line of code that BlocProvider has taken care of automatically, it is useful to potentially choose whether to actually dispose that Bloc’s resources.

For example, if you are switching to a new context by navigating to a new page or showing a dialog, you may need to manually pass the bloc instance into the new context to access it. If you want to make it available to all children of that page / dialog, without necessarily disposing of it when you close the dialog or navigate back, you can still use Provider and just leave out the dispose method.

For anyone who already uses Provider, this may seem obvious. It is really pretty simple and once you start using it, it can quickly become the go-to way to cleanly wire up some interface for any kind of logic to the UI.

And, just for the record, Flutter isn’t going anywhere.

Sharing a Bloc Between Contexts

The Bloc Provider class I have been using, which I took from this article, is not the only way to propagate the bloc down a widget tree. What it does do is provide an easy reference to any bloc instance that was created higher up in the widget tree. That is just by calling BlocProvider.of<BlocType>(context).

This doesn’t work, though, if the context is different than the context in which the bloc was created. This might be the case if we use showDialog(), because:

 The widget returned by the builder does not share a context with the location thatshowDialog is originally called from.

docs.flutter.io/flutter/material/showDialog.html

This might be confusing because showDialog() requires two arguments: a context (the current context) and a builder, which is a method that takes in a context and returns a Widget. These are different contexts. The second, the context the dialog is being built in, can not be used to access the bloc instance from the original context.

This presents a slight problem because we want to be able to show a dialog that presents some options for the user to fill out – including, if it is a payment, to which user the payment was made. So we will need a list of the users in the account, which is a field in the accountBloc, in order to create that DropDownButton.

The first thing I tried was just creating a stateful widget with the form options and fields that was inside a Dialog widget. The Dialog is important because there needs to be a MaterialWidget ancestor, which would normally be the Scaffold, but we are now in a new context that the Scaffold is not a part of, so we need something else. A Dialog() widget will do the trick, and will wrap whatever we put inside of it in a plain material box raised above the shadow of the context we left behind, down below.

Now, to create the DropDownButton, we will need a list of type DropDownMenuItem that will be the items in our list. Each DropDownMenuItem() has a child: which will probably just be a Text widget with the name of the option, and a value: that is passed whenever that item is selected.

The easiest way to do this will be to call .map() on the list of users in the account to turn them into a list of DropDownMenuItems. But, we can’t access the bloc that has the users in the dialog’s context, because it is a different context.

We can, however, pass a reference to the bloc we are interested in to the constructor of the widget in showDialog(). To fully implement the Bloc pattern, we should make the dialog a stateless widget and handle all state in its own bloc that resides in the dialog’s own context. This new bloc will still need a reference to the bloc outside the dialog, and it can be passed in from the context in which the dialog was triggered, through the constructor of the dialog widget, and into the dialog bloc’s constructor.

That will look something like this:

//Original context
FloatingActionButton(
  onPressed: () => showDialog(
    context: context,
    builder: (newContext) => NewEventDialog(
      accountBloc: BlocProvider.of<AccountBloc>(context),
    ),
  ),
  child: Icon(Icons.add),
),

...

//New Context
class NewEventDialog extends StatelessWidget {
  final AccountBloc accountBloc;
  NewEventBloc _newEventBloc;

  NewEventDialog({this.accountBloc}) 
    : assert(accountBloc != null);

  @override
  Widget build(BuildContext context) {
    _newEventBloc = NewEventBloc(accountBloc: accountBloc);

    return BlocProvider(
      bloc: _newEventBloc,
      child: Dialog(

      ...

Cross-Instance Reactivity with Streams and Futures in Firestore

Since this app is designed for a group of people to work together in real-time, it is important that when someone enters a bill or payment record into the database, that record is immediately visible to anyone else who is using the app. Unfortunately, if we just use Futures to get all of the data from our database when we think we need it (like when the app starts or when that user submits some new information) those methods will only be called when they are explicitly told to by that instance of the app. We could have a refresh button that triggered all of our data to be re-fetched in case anything has changed, or just rely on our users to eventually close and re-open the app if they want the most updated information. This may work for a hobby project but today’s consumers expect more out of the software they use – especially software they pay for.

So the challenge is to ensure that the critical information is always being listened to in case it changes. Fortunately, Flutter and Firestore provide all of the tools we will need to do just this. The general strategy will be to listen to a function we write in our database layer that returns a Stream<QuerySnapshot> or Stream<DocumentSnapshot>, and in the event something changes in that Query/Document, a new snapshot will be piped into a method in our repository that will convert that DocumentSnapshot or QuerySnapshot into a Map, or whatever data object we need, and then send it directly to the Bloc as a Stream. The Bloc will be listening to that stream and will be ready to react to any changes that come down the pipe.

For example, we have the list of accounts and connection requests for a particular user on that user’s document, which is in the ‘Users’ collection at the root of the database. When a connection request is approved by the administrator of the account you are trying to connect to, that will delete the ‘connection request’ entry on the user document and write the account id to the user’s ‘accounts’ list. That change is propagated by the administrator of the account, which is a separate instance of the app from the user who is trying to connect to the account. So the connecting user will want to be listening to their own user document, and when a change is made, update the data in the Bloc and rebuild the UI.

Since before we were doing something like, “make the change in the database, then rebuild the UI” and we are adding, “when a change is made in the database, rebuild the UI”, we will want to delete part of what we were doing before so we do not unnecessarily rebuild the UI, or rebuild it before we receive the most current info from the database. Essentially, we will have two parallel processes – one reacting to changes in the app’s interface and sending information up to the database, and a separate process that is listening to the database and sending information down to the UI.

Another issue this brings up is that we will sometimes want to modify multiple fields on a single document but treat it as a single ‘event,’ so it doesn’t trigger the UI to rebuild with each line of the update, but just at once when it is all updated. It is easy to do this by adding the individual changes to a WriteBatch object and then committing it all at once instead of dealing with individual Futures.

Database Layer

Among other things, it will be important to listen to the particular user document of the user who is signed in, and the collection of users who belong to the currently selected account. In the database layer, these methods look like this:

Stream<DocumentSnapshot> currentUserStream(String userId){
  return _user(userId).snapshots();
}

Stream<QuerySnapshot> userStream(String accountId) {
  return _usersCollection.where(ACCOUNTS, arrayContains: accountId).snapshots();
}

In the first method, I am just streaming the particular document that is identified by the userId. When this stream is listened to or when that user document is modified, a DocumentSnapshot will be piped through the stream to any methods which are listening to it. In the second method, I am actually searching through all of the user documents in the collection for those who have a particular accountId in their ‘accounts’ array. Whenever a new user has that id added to their accounts array, a new QuerySnapshot that contains a list of all of those users will be piped through the stream to be listened to.

If we want to modify multiple fields on the user document at once, we will need to use a WriteBatch object to avoid triggering the methods listening for changes to that document more than once:

Future<void> addAccountToUser(String userId, String accountId, String permission) async {
  WriteBatch batch =_firestore.batch();
  DocumentReference user =_user(userId);
  DocumentSnapshot userSnapshot = await user.get();

  batch.updateData(user, {ACCOUNTS: userSnapshot[ACCOUNTS] + [accountId]});

  Map thisAccount = {PERMISSIONS:[permission]};
  Map userAccountsInfo = userSnapshot.data[ACCOUNT_INFO];
  userAccountsInfo[accountId] = thisAccount;

  batch.updateData(user, {ACCOUNT_INFO:userAccountsInfo});

  return batch.commit();
}

Repository Layer

These streams are piped through our repository layer where they are adjusted to be the data type that our Bloc can easily work with. We can do this by using the map() function which we can call directly on the stream:

Stream<Map<String, dynamic>> currentUserStream(String userId){
  return _db.currentUserStream(userId).map((document) {
    document.data[ID] = document.documentID;
    return document.data;
  });
}

Stream<List<Map<String, dynamic>>> userStream(String accountId){
  return _db.userStream(accountId).map(
    (snapshot) => snapshot.documents.map(
      (document) => document.data
    ).toList()
  );
}

In the first Stream, we are retrieving a single user but we will need to save the documentId in the map we pass through. So, we can modify the data before returning it by adding an ‘Id’ field (ID is a string constant, ‘Id’). In the second Stream, we are converting a QuerySnapshot, which potentially contains many individual DocumentSnapshots, into a List of Maps. So, we call map first to convert the QuerySnapshot into a list, and map on each item in the list to convert it into a Map<String, dynamic> instead of a DocumentSnapshot.

One advantage of cleansing our streams of the DocumentSnapshot and QuerySnapshot types is that we don’t have to import the firestore library into the Bloc class to parse those types there. This is what we want because the Bloc layer should only be dependent on the Repo layer, and the repository only dependent on the data layer.

Bloc Layer

Here is where the magic happens. The key will be to keep separate (1) the listening and responding to changes in the database from (2) responding to UI events. For example, to request a connection to an account, first the user enters the account name and clicks the ‘submit’ button, which sends a RequestConnectionEvent with the request account name to the Bloc. From there, it looks like this:

void _requestConnection(String accountName) async {
  _accountStateSink.add(AccountStateLoading());

  dynamic accountIdOrNull = await repo.getAccountByName(accountName);
   
  if(accountIdOrNull != null){
    bool newAccount = !currentUser.accounts.contains(accountIdOrNull);
  
    if(newAccount){
      //update the user document
      repo.createAccountConnectionRequest(
        accountIdOrNull, 
        currentUser.userId
      );

    } else {
      _accountStateSink.add(
        AccountStateSelect(
          error: 'You are already connected to $accountName'
        )
      );
    }
  } else {
    _accountStateSink.add(
      AccountStateSelect(
        error: '$accountName does not exist' 
      )
    );
  }
}

So, the first thing this does is set the state to ‘AccountStateLoading()’, which sets the whole screen to a loading screen. Ideally, we might just want to display a small loading icon in the corner of the screen but otherwise keep the account page visible. The important thing is that in response to the UI event, the loading state is set, we check if the account is already connected to, and if not, we just call the method in the repository which sets the information on the user document. If there is no error, we never actually do anything to get out of the loading screen in this process. If we never heard back from the database, we would stay in the loading screen forever (which is another issue that will eventually have to be fixed, but in most cases, will not matter).

Because the user that is being modified is the currentUser, though, once the database is updated it will trigger the method that is listening to the current user stream:

// in the Bloc's constructor, we define the StreamSubscription 
// _userSubscription:

    _userSubscription = repo.currentUserStream(authBloc.currentUserId).listen(
  _updateCurrentUserAndAccountNames
);

...


void _updateCurrentUserAndAccountNames(Map<String, dynamic> user) async{
  User userFromSink = User.fromJson(user);
  //check to see if we need to update accountNames
  if(currentUser != null){
    if(currentUser.accounts != userFromSink.accounts){
      accountNames = 
        await repo.getAccountNames(userFromSink.accounts);
    } //otherwise they are still accurate
  } else { //no current user, so we need names from this one
    accountNames =
      await repo.getAccountNames(userFromSink.accounts);
  }
  currentUser = userFromSink;
  _goToAccountsOrSelect();
}

void _goToAccountsOrSelect() {
  if(currentUser.accounts.length == 1){
    accountEvent.add(AccountEventGoHome(accountIndex: 0));
  } else {
    accountEvent.add(AccountEventGoToSelect());
  }
}

Once the new user with a new ‘connection request’ comes down the pipe, it will check out the new user and eventually trigger a new Account Event that will cause the UI to rebuild with the new currentUser information. The bloc checks stuff like if a new account id is in the list of accounts (which would be the case if a connection request was just approved), in which case a database call will need to be made to retrieve the name of that account in order to display the name and not just the documentId. Once the new event is added to the Event Stream, that will lead to the original pipeline in the bloc, _mapEventToState, and the UI will rebuild with the new State object.

Costs and Benefits

We will want to use this technique in combination with explicit database calls to balance performance, accuracy, reactivity, and bandwidth.

For information like the current user, we will want to constantly be listening to that because the normal use case of the app involves a separate instance, the account administrator, modifying the user document while the user is potentially logged in. We pay a price by defining this StreamSubscription, though, and if we did this everywhere for every piece of data in the entire app, we might create a suboptimal user experience or worse, make the app impossibly frustrating and slow.

There are other instances, though, when we would not expect the information we retrieve to change in another instance, like the name of the account a particular user is connected to. There may be times when that information does change, like, if a user requests a connection to an account, then the account name changes, but the list of connection requests still displays the old account name. I just don’t foresee that leading to any particular confusion or frustration, so I don’t think it is worth it to stream the names of the accounts for which there are connection requests.

Summary

When we created our first Blocs, the basic components were Event objects, State objects, Streams of the events and States pulled from a StreamController we instantiated when creating the bloc and disposed of when the bloc is disposed, and the _mapEventToState method that we invoked by listening to the Event stream. Methods in the repository that modified the database were called in this Event -> State pipeline. Now this basic recipe is getting a little more complicated by adding another listener, this time to a stream we define in the repository layer. This listener triggers a method like _mapEventToState, except it maps the data that is being listened to to an event, which is then mapped to the new state via the original pipeline. This allows us to simplify some of the paths through the _mapEventToState, because the modifications to the database will now trigger the event via the bloc’s new listener.

Stream Queries In Ways You Wouldn’t Think Would Work

The time has come to actually display data that is associated with the account. Hopefully we can do that in a way that takes advantage of Cloud Firestore’s strengths as a database.

The goal is to have users be able to create an account, for other users to be able to request a connection to that account, and for the account owner to be able to approve that connection, adding the new user to the account. So, to start, we are just going to try displaying the list of users in the account. Easy, right? Instead of just going with the first solution we think of, though, we would be better served to think of a variety of solutions and justify why the one we eventually pick is the best. So first we’ll discuss how queries in Cloud Firestore are different from other databases, and how we might structure our queries to take advantage of this.

Since I am trying to keep all user information for the entire application in one Users collection, a user is identified as belonging to an account only by having that account’s id in an ‘accounts’ list on the user document. So in order to display the list of users who belong to a particular account we will need to filter all of the users in the Users collection and only get the ones who have a particular id in their ‘accounts’ array.

Keep in mind I’m still learning, though, so this is more of a (Document)Snapshot of the learning experience than any kind of definitive reference.

Querying in Cloud Firestore

Thanks to how Cloud Firestore is structured, this is not just possible, it is efficient. Firestore has the extremely handy feature that:

Queries scale with the size of your result set, not the size of your data set, so you’ll get the same performance fetching 1 result from a set of 100, or 100,000,000.

Alex Dufetel, Firebase Project Manager

In other words,

If you request 10 items out of 1 million items, it will take the same time as requesting 10 items out of 100 million items.

This is because of the way Firestore’s query system is designed. While you may not be able to model every query directly from a relational data model to the Firestore data model, if you can define your use-cases in terms of a Firestore query it is guaranteed to execute in a time relative only to the number of results you request. 

Frank Van Puffelen, Firebase Engineer

So, we could have 100 million user documents in our Users collection. Each user document has an array of accountIds that the user is associated with. If we want to find the 10 users out of the 100 million who are associated with a particular accountId, it will be just as easy to query the 100 million user-collection for the 10 we are interested in as it would be to find the same 10 users in a collection of 20. Apparently this has something to do with how Firestore indexes the documents within collections. We wouldn’t be able to run that kind of query, for example, on groups of documents that existed in different collections.

Streams vs. Futures

When we are handling the database references, until now we have created and fetched data with methods that return Futures of some concrete type, like a Future<Map<String, dynmaic>>. We can do this by marking the function with the async keyword and chaining database commands with .then((result) { return …}), so the original DocumentSnapshot or QuerySnapshot can be parsed into the data we are interested in. We can chain these database commands together in our repository, so we would call something like createAccount().then((_) => getAccounts()). This would wait for createAccount to finish, then execute the getAccounts method to update the list of accounts to display.

This approach will have some major limitations once we have multiple people interacting in one account, though, or a user is using the app on multiple devices. While we can call getAccounts() when we create the page, and when the user does something to modify the list of accounts, we would have no way of knowing if the user has since logged into their account on their tablet and created an account in that instance. Once several users are logged into one account and are sharing payments or bills, we will obviously want each instance of the app to be updated as soon as a new bill is submitted by any one user on that account.

Instead we will want each instance connected to a Stream that is notified every time the data that stream is listening to is modified or added to. So, when you open your app, your roommate can connect, submit a bill, and you should be able to see the information being updated on your home page without having to do anything to refresh it.

The query for a particular account’s users looks like this:

CollectionReference get _usersCollection 
  => _firestore.collection(USERS);

Stream<QuerySnapshot> userStream(String accountId){
  return _usersCollection.where(
           ACCOUNTS, 
           arrayContains:accountId
         ).snapshots();
}

This stream is passed through untouched in the repository:

Stream<QuerySnapshot> userStream(String accountId){
  return _db.userStream(accountId);
}

And listened to in the bloc:

StreamSubscription _subscription;

...

AccountUsersBloc({this.accountId}){
  assert(accountId != null);
  _subscription = repo.userStream(accountId).listen(_addUsersToList);
}

...

//don't forget to cancel when you are done
void dispose() {
  _subscription.cancel();
  ...

Now, any time a new user has the account Id being listened for added to their ‘Accounts’ field, this stream will be updated and _addUsersToList will be called with a new QuerySnapshot containing the newly updated list of user documents. This method translates the DocumentSnapshots into the raw data (usernames), and there is a separate Stream in the bloc that passes these usernames to the UI to render into ListTile widgets.

Is This Really How This Works?

This sounds kind of ridiculous when you think about it, especially if we stretch our imagination and ask if this would hold up if 100 million users were using the same database. Would a single user updating their list of accountIds in their accounts field really send the signal to whatever handful of other users out of 100 million happened to be listening for a list of users who had that particular id in that particular array that the results of that extremely specific query have been updated?

I think so. I don’t know for sure, to be perfectly honest. And I certainly don’t understand how or why it works. But that is what I believe is being advertised about Cloud Firestore, so we are going to try it.

Creating a New Account

There is a bit of set up to create a new account. We will check that the account name is unique in the database, we will create a new account document and return the id, and we will also need to designate the user who created the account as the owner through some kind of permissions structure.

Unique Account Names

Account names don’t need to be unique necessarily, since they will have distinct accountIds, but enforcing uniqueness for account names will make it easier for users for find each others’ accounts.

In the repository, I created a function that returns a bool value depending on the name it is given:

Future<bool> doesAccountNameExist(String name){
  return _db.getAccountsWhere(NAME, name).then(
    (documents) => documents.length != 0
  );
}

And in the data provider, we define the function getAccountsWhere:

Future<List<DocumentSnapshot>> getAccountsWhere(String field, val){
  return firestore.collection(ACCOUNTS).where(
    field, 
    isEqualTo: val
  ).getDocuments().then(
    (query) => query.documents
  );
}

So, if the account name already exists when the user tries to create a new one, we can display an error instead of going ahead with the account creation.

Creating and Returning the Account Id

When we create the account document, we will use an automatically generated id to identify the account, just by calling .document() without any parameters on the CollectionReference. This ensures the account document is unique and well organized in the database. Eventually, though, we will want to know what the account Id is so we can continue to make database calls to that account.

So instead of just creating the account like this:

Firestore firestore = Firestore.instance;
CollectionReference get accountCollection => firestore.collection(ACCOUNTS);

Future<void> createAccount(String actName) async {
  return accountCollection.document().setData(
    {NAME: actName}
  );
}

We would be better served by returning the id of the newly generated document by splitting up the getting and writing into separate futures, like this:

Future<String> createAccount(String actName) async {
  return accountCollection.document().get().then(
    (document){
      return accountCollection.document(document.documentID)
             .setData({NAME: actName}).then(
        (_){
          return document.documentID;
        });
    });
}

Best place for user’s permissions on a particular account?

Options for organizing the user information in our database. Do we need both user collections? If not, what is the best way to associate the users with the accounts?

When I first sketched out the database, I designated the ‘owner’ permission on a separate user document under the new account document. It seems clunky, though, to have a separate user collection under the account document when there is already a user collection at the root of the database. We have the root-user collection because the user needs to be able to sign in before they create an account, and a user might be associated with multiple accounts. Do we also need a collection under the account document of separate user documents for the users in that account?

One reason we might think it is necessary to have a separate user collection under each account is that every time someone logs in to their account, we will need to get the list of users in that account and information related to that user’s relationship with that particular account, like the dates they are responsible for bills or what their share in those bills is. We will want to show their names, so we could also duplicate their username and just keep the information in both places. It would be easy to just retrieve the list of users that are in their own, neatly-packed collection that already had the information we want all ready.

I do not think this is a good enough justification for having two separate user collections, however. There are ways around this: we could either (1) keep a list of id’s on the account document for the users in the account, or (2) user a clever firestore function to get the users for a particular account whenever we need them. And when we need to set the user’s permissions or other account-specific information, we can store that information as a map under the user document between the accountId and the permissions object.

A better argument for having a separate user collection under each account might be that the information we would store in the second (account-user) collection is categorically different than the information we are storing in the first (root-user) collection. The account-user document describes the relationship between that particular user and that particular account, while the root-user document just describes the user’s information that relates to the user, like their name. (This raises the question of whether users might want to have separate usernames for separate accounts, as someone might be called Alex at work but Alexa with friends … )

I think this is probably a good enough justification for keeping the account-user information separate from the root-user collection if we felt particularly compelled to do so, but if it is possible to have a single user document in the database rather than keeping the user’s information in multiple places, I think that would be safer and more efficient in the long run. It would be reasonable to have, essentially, a map of maps (most likely a map with a single entry in it) on the root-user document that describes the relationship between the user and her accounts. We will have this information as soon as we log in and first retrieve the user’s information from the root collection. This will save us database calls and structural complexity, hopefully making the app faster and more efficient to build and operate and reducing the chance of an error due to information getting out-of-sync.

Changing a User’s Name

We want to give people the ability to forge their own identity and feel some ownership of the app. A good way to do this is to give the user the option to name themselves whatever they want. Since we are identifying users internally using a userId, it really doesn’t matter what the username is. We will just retrieve it whenever we need to show it, and it can be changed without worrying about changing references to that user.

When we create the user, we are creating it in firebase_auth, and then taking the Id that is generated from creating the user there and using it to create a database entry that has more information about that user, like the username. When a user logs in successfully with their credentials, a FirebaseUser object is returned that has the userId field that we will use to store and retrieve the user information from Firestore.

So in the data provider layer, we have a method that pinpoints the user in the database and updates the designated field:

Future<void> updateUser(String userId, String field, data) async {
  return firestore
      .collection(USERS)
      .document(userId)
      .updateData({field: data});
}

And we can call this method in the repository layer like this:

Future<void> updateUserName(String userId, String name){
  return _db.updateUser(userId, NAME, name);
}

We’re going to use the account selection/creation page to put the rename username widget, but it will be easy to move around if we decide later there is a better place for it. In the AccountBloc, we can define a new event type:

class AccountEventRenameUser extends AccountEvent {
  final String newName;
  AccountEventRenameUser({this.newName}) 
    : assert(newName != null);
}

When this event comes through, we will want to notify the repository to update the username in the database, but we will also want to rebuild the UI to show the new username. The order of events here is important. We will want to save the new username to the database, wait for that operation to complete, then read the new username in the database and, only then, rebuild the UI. In the Bloc, we can get this clear progression of operations by using either the ‘await’ keyword or .then((input) => function) notation.

void _renameUser(String username){
    _accountStateSink.add(AccountStateLoading());
    repo.updateUserName(currentUser.userId, username).then(
      (_) => repo.getUserFromDb(currentUser.userId)
    ).then(
      (user) {
        currentUser = user;
        _accountStateSink.add(AccountStateSelect());
      });
}

Since updateUserName returns void, we just use the underscore to indicate there is something being returned but we don’t care about it. When we getUserFromDb, however, we do want to operate on what is returned, so we name the return object (user).

The widget that is responsible for renaming the user looks like this – we get a reference to the bloc, define a TextEditingController such that the initial text is the current username:

AccountBloc accountBloc = BlocProvider.of<AccountBloc>(context);

TextEditingController _theController = TextEditingController(text:accountBloc.currentUser.userName);

return Column(
   crossAxisAlignment: CrossAxisAlignment.start,
   children: <Widget>[
    Text("Username:"),
    Container(
      width: 200.0,
      child: TextField(
        controller: _theController,
      ),
    ),
    FlatButton(
      child: Text('Submit'),
      onPressed: () => accountBloc.accountEvent
          .add(AccountEventRenameUser(
            newName: _theController.text)),
    )
  ],
);

Since this is below the Account Page on the widget tree, which listens to the account state stream, when a new AccountStateSelect is passed through the account state stream, this widget will be re-built and the new username will be reflected in the text field.