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');
}
}
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:
- 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, aStream
or aChangeNotifier
or any other reactive class. - We must create a special widget that is going to listen to the changes of the
Publisher
. - This widget must invoke functions pushed to the
Publisher
with the latestBuildContext
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(),
);
}
}
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.
Top comments (0)