Bloc – Is it possible to yield states for a previous page in the navigation stack?

Issue

I have a BlocBuilder which handles building widgets depending on the yielded state for my dashboard page.

body: BlocBuilder<DashboardBloc, DashboardState>(
        builder: (context, state) {
          print(state);
          if (state is DashboardInitial) {
            return loadingList();
          } else if (state is DashboardEmpty) {
            return emptyList();
          } else if (state is DashboardLoaded) {
            return loadedList(context, state);
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
              context, MaterialPageRoute(builder: (context) => AddPage()));
        },

I want to be able to navigate to the add page, fill in some textfields, and then dispatch an event to my dashboard bloc, with the idea being that upon navigating back to the dashboard, my list will be updated.

class AddPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    TextEditingController titleController = TextEditingController();
    TextEditingController descriptionController = TextEditingController();
    return Scaffold(
      appBar: AppBar(title: Text('Add')),
      body: Container(
        padding: EdgeInsets.all(10),
        child: Column(
          children: [
            TextField(
              controller: titleController,
            ),
            TextField(
              controller: descriptionController,
            ),
            RaisedButton(onPressed: () {
              BlocProvider.of<DashboardBloc>(context)
                  .add(DashboardWorryAdded('title', 'description'));
            }),
          ],
        ),
      ),
    );
  }
}

When following the code using breakpoints, i am able to see that my state is yielded in the ‘mapeventtostate’ function, however my dashboard is never rebuilt with the new values.

Here is the code for my Bloc, events, and states. My first thought would be that Equatable was detecting the same state being returned, but upon removing Equatable, my problem is still persists.

 @override
  Stream<DashboardState> mapEventToState(
    DashboardEvent event,
  ) async* {
    if (event is DashboardWorryAdded) {
      yield* _mapDashboardWorryAddedToState(event);
    } else if (event is DashboardLoading) {
      yield* _mapDashboardLoadingToState(event);
    } else if (event is AppStarted) {
      yield* _mapAppStartedToState(event);
    }
  }

  Stream<DashboardState> _mapAppStartedToState(AppStarted event) async* {
    List<Worry> _wList = await repo.getAllWorries();
    if (_wList.length != 0) {
      yield DashboardLoaded(worryList: _wList);
    } else {
      yield DashboardEmpty();
    }
  }

  Stream<DashboardState> _mapDashboardLoadingToState(
      DashboardLoading event) async* {
    List<Worry> _wList = await repo.getAllWorries();
    if (_wList != 0) {
      yield DashboardLoaded(worryList: _wList);
    } else {
      yield DashboardEmpty();
    }
  }

  Stream<DashboardState> _mapDashboardWorryAddedToState(
      DashboardWorryAdded event) async* {
    await repo.addWorry(event.title, event.description);
    List<Worry> worryList = List<Worry>();
    worryList = await repo.getAllWorries();
    yield DashboardLoaded(worryList: worryList);
  }
}
@immutable
abstract class DashboardEvent {}

class DashboardLoading extends DashboardEvent {
  DashboardLoading();
}

class DashboardWorryAdded extends DashboardEvent {
  final String title, description;

  DashboardWorryAdded(this.title, this.description);
}

class AppStarted extends DashboardEvent {
  AppStarted();
}
@immutable
abstract class DashboardState {}

class DashboardInitial extends DashboardState {
  DashboardInitial();
}

class DashboardLoaded extends DashboardState {
  final List<Worry> worryList;

  DashboardLoaded({this.worryList});
}

class DashboardEmpty extends DashboardState {
  DashboardEmpty();
}

Solution

Instead of trying to mutate another page’s state (a bit of a no-no where state management is concerned), take advantage of the fact that the push method of the navigator returns a future that completes when that page gets popped, and as a bonus, the value of the future will include the value that was given to the pop method in the other page. So you can now do something like this:

class DashboardBloc {
  ...

  void showAddPage() async {
    // Do this to retrieve the value passed to the add page's call to `pop`
    final value = await Navigator.of(context).push(...);

    // Do this if the add page doesn't return a value in `pop`
    await Navigator.of(context).push(...);

    // Either way, you can now refresh your state in response to 
    // the add page popping
    emit(...);
  }
}

Note: This works just as well for named routes too.

Answered By – Abion47

Answer Checked By – Jay B. (FlutterFixes Admin)

Leave a Reply

Your email address will not be published.