Craig Edwards
Craig Edwards

Reputation: 2292

Combining flutter bloc forEach() with another await

Imagine I've got a (contrived) state that looks something like:

class UserState {
  final String? name;
  final String? address;
}

The source for name comes from a single (asynchronous) API call, but the address field value comes from a stream. Conceptually, the bloc might emit the following:

UserState(name: null,   address: null)
UserState(name: null,   address: 'New York')
UserState(name: 'john', address: 'New York')
UserState(name: 'john', address: 'Smith St, New York')
UserState(name: 'john', address: '10 Smith St, New York')

The screen that receives these states is perfectly capable of rendering itself based on the presence (or not) of name and address.

I can see that there's emit.forEach() that looks like it would be perfect for dealing with the stream, but I'm wrestling with how to deal with awaiting for the fetchName() API call.

I thought of having a local variable which keep track of whether name has came back (something like the following) but there's a scenario where fetchName() is slow and doesn't return until after all the address stream events have finished - which means I miss a state.

on<FetchUserEvent>((event, emit) async {
  String? name;
  fetchName().then((n) => name = n);

  emit.forEach(
    addressStream,
    onData: (a) => UserState(name: name, address: a)
  );
});

Is my only option to have to import RxDart just to pick up something like "combine latest"?

Am I "doing it wrong"?

Upvotes: 2

Views: 3083

Answers (2)

slaci
slaci

Reputation: 190

The two calls should be separated into different events. Bloc dispatches events concurrently by default, so the following should work:

In the bloc:

on<FetchUserEvent>((event, emit) async {
  final name = await fetchName();
  emit(UserState(name: name, address: state.address));
});

on<ListenAddressEvent>((event, emit) async {
  await emit.forEach(
    event.addressStream,
    onData: (newAddress) => UserState(name: state.name, address: newAddress),
  );
});

And the bloc provider widget dispatches the two events eg. on create:

BlocProvider(
  create: (context) => UserBloc()
    ..add(FetchUserEvent())
    ..add(ListenAddressEvent()),
);

You need to use await before emit.forEach, otherwise it won't really listen. It throws an error about it if you forget as far as I remember.

Just to make sure: in this example the FetchUserEvent and the ListenAddressEvent events will start together if you use at least bloc 8.0.

Upvotes: 0

on<FetchUserEvent>((event, emit) async {
  String? name = await fetchName();

  emit.forEach(
    addressStream,
    onData: (a) => UserState(name: name, address: a)
  );
});

Upvotes: 0

Related Questions