Main question
Hello! In Flutter I am using BLoC, and more specifically cubits, to manage the state of my app.
I have a few states in my weather_state.dart
file:
- WeatherInitial
- WeatherLoading
- WeatherLoaded
- WeatherError
For the latter, I would like to pass an error message when the state is emitted, as you can see in this method, from weather_cubit.dart
(last line):
Future<void> getWeather(String cityName) async {
emit(const WeatherLoading());
try {
final weather = await _weatherRepository.fetchWeather(cityName);
emit(WeatherLoaded(weather));
} on NetworkException {
emit(const WeatherError('Error fetching the data.'));
}
}
Now, I just localized my app, and therefore wanted to localize the error message as well. I imported the necessary packages in my cubit, and then accessed the localized error message that way:
emit(WeatherError(AppLocalizations.of(context)!.errorFetchingData));
I get the following error: Undefined name 'context'.
.
I understand why, obviously, but my question then is:
How do I access the context from inside a cubit?
And if there are multiple ways, what is the most optimized one to do so?
PS: If you have a decent workaround, feel free to mention it, too.
What I found:
Apparently, from this ressource, I found that providing the context that way (here, in my home_screen.dart
):
return BlocProvider<WeatherCubit>(
create: (context) => WeatherCubit(),
child: MaterialApp(
...was supposed to make it accessible in my cubit. It does not work for me, as you can guess. By the way, I am searching for something a bit better than providing the context as a parameter in the cubit constructor (because that would be a real pain for testing afterwards).
Edit
I know I can pass an error message as a String parameter in my method. I also already use an error message property in my WeatherError state.
My problem is that the error message depends on the behavior of the method itself, which does not make possible passing an argument inside.
I took a simple example before, but I will provide another one there:
Suppose I have a UUID that I'm getting from a QR code scan.
I am passing the UUID as a parameter of the selectCity method:
void selectCity(String? cityUuid) async {
const uuidPattern =
r'[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}';
final regexUuid = RegExp(uuidPattern);
if (cityUuid == null) {
emit(CitySelectionError('The QR code scanner had an error. Please try again.'));
} else if (!regexUuid.hasMatch(cityUuid)) {
emit(CitySelectionError('The QR code does not contain a UUID and cannot match a city.'));
} else {
emit(CitySelected(cityUuid));
}
}
The error state:
class CitySelectionError extends CityState {
final String errorMessage;
CitySelectionError(this.errorMessage) : super(null);
// Equatable: don't mind
@override
List<Object?> get props => [errorMessage];
}
I first want to check if it is null, as this would be a result of scanning failure, before checking if the string is an actual UUID.
As you can see, the error messages clearly depend on the method implementation. This implies that I cannot pass the error messages as parameters; this would not make any sense.
Any idea about what I am missing?
CodePudding user response:
Idk if it's a good idea to send the context (let's leave the context for the tree to your widgets, not in the logics), in my case I only send the error without the message :
emit(const WeatherError());
Now, in a listener (in case you want to show a Snackbar) you listen to that state and there you show the translation:
return BlocListener<BlocA, StateA>(
listener: (context, state) {
if (state is WeatherError) {
ScaffoldMessenger.of(context)
..showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.errorFetchingData),
),
);
}
},
child: ...
CodePudding user response:
The error is normal because Cubit
has nothing to do with Widget
and is not responsible to build
anything.
How do I access the context from inside a cubit?
To strictly answer your question, in order to use the context
inside the Cubit
, you can pass it as an argument when creating the Cubit
.
Something like follow :
return BlocProvider<WeatherCubit>(
create: (context) => WeatherCubit(context),
child: MaterialApp(
And so WeatherCubit
should look like :
class WeatherCubit extends Cubit<WeatherState> {
final BuildContext context;
WeatherCubit(this.context) : super(WeatherState());
}
But, if the aim is to display the error message to the user, for example in a SnackBar
, I recommend you to use a BlocListener
(or a BlocConsumer
if you need a BlocBuilder
too) to handle this side-effects. By doing so, you keep a clear separation of responsibilities and respect the BLoC pattern's philosophy.
BlocListener<WeatherCubit, WeatherState>(
listenWhen: (previous, current) => previous != current,
listener: (context, state) {
if (state is WeatherError) {
ScaffoldMessenger.of(context)..hideCurrentSnackBar()..showSnackBar(SnackBar(content: Text(state.errorMessage)));
}
},
child: child,
),
And finally, the WeatherError
:
class WeatherError extends WeatherState {
final String errorMessage;
const WeatherError({required this.errorMessage});
}
That way you can access the errorMessage
with state.errorMessage
when the current state is WeatherError
.