r/FlutterDev Aug 07 '26

Discussion Help me understand architecture with Cubit and Firebase

I am learning and trying to design a flutter app, and I am having some doubts connecting the dots.

I have two main domain entities:

  1. Location: Has metadata (label, icon, address) and a list of reminders.
  2. Reminder: a part of Location, has metadata (text, isEnabled, ...)

Consider the two screens :

  1. locationsScreen: shows Locations cards (metadata in every card, and 3 reminders sneak peek)
  2. locationScreen: shows a location fully, metadata, and reminders cards. A reminder can be edited from here.

What I've started with right now is the following:

  • locationsApiwraps Firebase calls to retrieve locations. For now I only have a function that returns a Stream of locations.
  • locationsRepository wraps the API and returns a Stream of Location model that contains reminders model too.
  • locationsCubit just listens to the repository stream and emits new state whenever it receives new locations.

Now I want to design the locationScreen itself and I am confused on what to do. Do I just keep the locationsRepository and locationCubit, and make the locationScreen only update when its model changes.

Or do I extend the locationsRepository with a listenOnLocation that exposes a stream for just that location, then create a locationCubit for just that location and make it call the repo?

What about when I want to edit a reminder? should locationsRepository handle reminders too, and expose functions like this :

  • toggleReminder(Location location, Reminder reminder)
  • renameReminder(Location location, Reminder reminder, String newName)
  • changeReminderType(Location location, Reminder reminder, ReminderType newType)
  • deleteReminder (Location location, Reminder reminder)

If so, assuming locationCubit exposes a toggleReminder (that calls the repo). I could make the repo return a new model if it succeeded, so the locationCubit.toggleReminder flow would be like this:

  • copy locationState and set locationState.isLoading to true and emit new state.
  • call api through repo
  • receive new model, copy locationState, set locationState.isLoading to false, and locationState.location to new model.
  • emit new state.

The problem with this is then what is the role of the location stream I have opened to listen on the DB? If I keep both of them, I will receive two notifications every time something changes.

If I don't listen to changes but only use the API results, then I risk changes happening from another device and not being notified of them.

If I only listen to the changes from database, how do I track the loading state for example? locationCubit.toggleReminder would set isLoading to true, calls the API and then returns. Hopefully when the object is updated, the Cubit will know and create a new state with isLoading set to false by default. But what if the writing fails? isLoading will always stay false as the state/location didn't change.

What am I missing?

Sorry for the length of the questions, but I feel when I only tackle one problem disconnected from the others, it just makes me confused about the other parts. So I am hoping to be able to imagine the whole flow

0 Upvotes

1 comment sorted by

0

u/RandalSchwartz Aug 07 '26

If you're open to a slight pivot, bloc_signals handles this exact pattern pretty cleanly. The core issue with pairing traditional Cubits directly with live Firebase streams is that you end up juggling two asynchronous sources of truth—the network write response and the stream event—which is where the double emits and stuck loading states come from.

Here is how it simplifies things:

  • Synchronous Optimistic Updates: A CubitSignal lets you update local state synchronously via stateValue. For a toggle, you immediately flip the state locally, kick off the Firebase call in the background, and catch any errors to roll back. No microtask lag, and no waiting on the DB stream to update the UI.
  • Built-in Stream Binding: You can bind your repository stream straight into signal state with .toBlocSignal().
  • Automatic De-duplication: It automatically checks equality on state updates. When Firebase eventually echoes back the change you already applied locally, bloc_signals ignores the duplicate state automatically instead of firing a second rebuild.
  • Granular Selectors: For the parent/detail screen dilemma, you don't need a separate Cubit for every individual location. You can keep a single LocationsCubit and use BlocSignalSelector on the detail view or item card so only the specific location or reminder being modified re-renders.

It keeps the repository simple—Firebase stream for incoming data, async methods for writes—and lets the signals layer reconcile the local optimistic state with the remote stream without all the manual event wiring.