Search code examples
flutterdartblocflutter-go-router

Access context after Navigator.pop();


I'd like to refetch the data when navigating back from Details screen -> List screen

 ReorderableListView(
    ...
    children: _localExercises.map((exerciseInTraining) {
      return _Exercise(
        key: UniqueKey(),
        ...
      );
    }).toList(),
    onReorder: (oldIndex, newIndex) {
    ...
    }
);
class _Exercise extends StatelessWidget {
  ...

  @override
  Widget build(BuildContext context) {
  return ListTile(
  ...
  onTap: () async {
  await context.pushNamed(ScreenNavigationKey.exerciseDetails)
      .then(
          //Here an error is thrown
          (value) => context.read<TrainerExerciseListBloc>().add(
              TrainerExerciseListEvent.fetchExercises(
              _clientId,
              _date,
          ),
}

Unfortunately, that is not possible because of an error:

E/flutter (26712): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.

E/flutter (26712): At this point the state of the widget's element tree is no longer stable.

E/flutter (26712): To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.

Debugger gives more info on the state of the context

StatelessElement#22e41(DEFUNCT)(no widget)

What - from what I read, means that list widget has been disposed/dismounted.

For FloatingActionButton on the same screen I use exactly the same pattern and it works just fine, it seem that flutter has a problem with list -> detail -> list type of navigation. Let me know if you need any details, it seems like a pretty common scenario.


Solution

  • DON'T use BuildContext across asynchronous gaps. If you have a linter configured in your project, you should probably have a warning message indicating that problem.

    You can modify your code to get the BLoC before pushing your route:

    class _Exercise extends StatelessWidget {
      ...
    
      @override
      Widget build(BuildContext context) {
      return ListTile(
      ...
      onTap: () async {
          // Get the BLoC from the context. 
          final bloc = context.read<TrainerExerciseListBloc>();
          // Push the screen
          context.pushNamed(ScreenNavigationKey.exerciseDetails).then((_) {
                  bloc.add(TrainerExerciseListEvent.fetchExercises(_clientId, _date);
              }
          );
          
       },
      ...
    

    This however could not always be triggered, since depending on your routing strategy (declarative vs imperative routing) the route which contains your _Exercise widget may no longer exists.