DEV Community

Mikhail Palei
Mikhail Palei

Posted on

How to use build context outside of Flutter widgets

Short answer:

If you need to use BuildContext outside of Flutter widgets use the package created based on ideas of this article:

https://pub.dev/packages/build_context_provider

Long answer:

When I started working with Flutter one of the most confusing things for me were inability to use methods that required BuildContext outside of flutter widgets.

Lets say you want to have a reusable class that navigates to a certain page of the app. You might want to use that class without a "context" argument. For example, in Clean Architecture use cases must never rely on any framework or library specific code. Since BuildContext is specific to Flutter framework this code is invalid in Clean Architecture:

class NavigateToProfileUseCase {
  call(BuildContext context) {
    Navigator.of(context).pushNamed('/profile');
  }
}
Enter fullscreen mode Exit fullscreen mode

What is the solution?

Note: the explanation of the solution is going to sound somewhat confusing. Please bear with me. It is going to get clearer by the end of the article.

In order to fix this problem we must change the way we call these functions. Instead calling functions directly we must pass these functions to the special widget that is going to run in UI layer.

Here is what we must do:

  1. We must create a special class which is going to hold a function. Let’s call it a Publisher. This can be a Cubit/Bloc, a Stream or a ChangeNotifier or any other reactive class.
  2. We must create a special widget that is going to listen to the changes of the Publisher.
  3. This widget must invoke functions pushed to the Publisher with the latest BuildContext instance.

Let’s take a look at an example:

class NavigateToProfileUseCase {
    call() { // <- notice how we don't pass BuildContext anymore
        functionRunnerCubit.runFunction(
        (context) => Navigator.of(context).pushNamed('/profiile'),
      );
    }
}

final functionRunnerCubit = FunctionRunnerCubit();

class FunctionRunnerCubit extends Cubit<FunctionRunnerState> {
  FunctionRunnerCubit() : super(FunctionRunnerWithNoFunctionsToRun());

  void runFunction(Function(BuildContext context) VoidCallback functionToRun) => emit(
        FunctionRunnerWithFunctionToRun({function: functionToRun}),
      );
}

class FunctionRunner extends StatelessWidget {
  const FunctionRunner({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocListener<FunctionRunnerCubit, ContextListenerState>(
        listener: (buildContext, state) {
          if (state is FunctionRunnerWithFunctionToRun) {
            state.callback(buildContext);
          }
        },
        child: SizedBox(),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Explanation of the code:

Inside of the UseCase we pass a function that we want to run with a BuildContext to the Cubit.

The Cubit publishes this function to the listeners.

FunctionRunner widget listens to the changes of the Cubit.

The BlocListener(think of it simply as a Listener if you don’t know the “bloc” library) invokes the function with a latest instance of the BuildContext.

In conclusion

This simple combination of a Publisher and a Listener will allow you to run your Flutter specific code anywhere. Such freedom will allow you to write simpler, cleaner and more maintainable code.

Latest comments (0)