Flutter BlocProvider of() called with a context that doesnt contain a bloc - flutter

My Flutter app shows an error:
The following assertion was thrown building BlocBuilder<AlgorithmBloc, AlgorithmState>(dirty, state: _BlocBuilderBaseState<AlgorithmBloc, AlgorithmState>#d1b56):
BlocProvider.of() called with a context that does not contain a GraphBloc.
The code of my main.dart:
MultiBlocProvider(
providers: [
BlocProvider<GraphBloc>(
create: (context) => GraphBloc(
graphRepository: graphRepository,
),
),
BlocProvider<AlgorithmBloc>(
create: (context) => AlgorithmBloc(),
),
],
child: MaterialApp...
This means that the BlocProviders are here. But when I go to my MainBody.dart file. I have nested BlocBuilders like this.
child: BlocBuilder<AlgorithmBloc, AlgorithmState>(
bloc: BlocProvider.of<AlgorithmBloc>(context),
builder: (context, state) {
if (state is SelectedAlgorithm) {
currentAlgorithm = state.algorithmName;
}
return BlocBuilder<GraphBloc, GraphState>(
bloc: BlocProvider.of<GraphBloc>(context),
builder: (context, state) {
if (state is EmptyGraph) {
BlocProvider.of<GraphBloc>(context).add(GetDefaultGraph());
return const Center(
child: CircularProgressIndicator.adaptive(),
);
}
Here is an Image from the error.
Bloc Error
Can anyone help me how to solve this problem?

the concrete class myClass is extended from Bloc which receive an event. The myClass has a stream method with an async* and yield putting data back on the stream after applying the business logic. A BlocProv manages communication between the myClass and the a Widget through context.bloc allowing the widget to communicate with the bloc stream through events and receive datastream data.

Related

Flutter bloc Cubit Bad state: Cannot emit new states after calling close

I have an app that I build using Cubit
I have two pages A and B.every thing works fine on its own. I use a change status cubit on both pages but when I move to the second page and pop to return to the first page I see the error on the title.
I inject dependencies using get it
route A
routes: {
'/home': (context) => MultiBlocProvider(providers: [
BlocProvider<ChangeStatusCubit>(
create: (context) => locator<ChangeStatusCubit>(),
),
], child: const TodoHomePage()),
Route B
'/details': (context) => MultiBlocProvider(
providers: [
BlocProvider<ChangeStatusCubit>(
create: (context) => locator<ChangeStatusCubit>(),
),
],
child: TodoDetailsPage(),
dependency injection
locator.registerLazySingleton<ChangeStatusCubit>(() => ChangeStatusCubit(
locator(),
));
cubit
changeStatus(int id) async {
emit(ChangeStatusLoading());
try {
ResponseModel response = await _changeStatusUseCase(id);
if (response.status == 200) {
emit(ChangeStatusLoaded(response.data));
} else {
emit(ChangeStatusError(response.error?.todo?.first ?? ""));
}
} catch (e) {
emit(ChangeStatusError(e.toString()));
}
}
When you use create to initialize the BlocProvider's bloc, the bloc's stream will be closed when that BlocProvider is unmounted.
To solve this, you can either move your BlocProvider higher up in the widget tree, so that it will remain mounted between pages, or you can use the BlocProvider.value constructor.
The latter is probably best, since you aren't actually creating a bloc, but making use of a pre-existing singleton. Though it would also make sense to move the widget higher up, so that it's a parent of the Navigator - that way you can declare it just once and reduce code duplication.
However, keeping the structure in your example, your code might look something like this:
routes: {
'/home': (context) => MultiBlocProvider(
providers: [
BlocProvider.value<ChangeStatusCubit>(
value: locator<ChangeStatusCubit>(),
),
],
child: const TodoHomePage(),
),
'/details': (context) => MultiBlocProvider(
providers: [
BlocProvider<ChangeStatusCubit>.value(
value: locator<ChangeStatusCubit>(),
),
],
child: TodoDetailsPage(),
),
}

Can you use BlocListener and BlocBuilder of different Bloc types in Flutter?

I have two BLoCs and need to listen for state changes from one in order to add events to the other and build the UI.
I think it's easier to explain in code:
#override
Widget build(BuildContext context) {
return BlocProvider<BlocA>(
create: (context) => BlocA(), <------ Create BlocA
child: BlocProvider<BlocB>(
create: (context) => BlocB(), <---- Create BlocB
child: BlocListener<BlocA, StateA>(
listener: (context, state) {
if (state is StateA) {
context.read<BlocB>().add(EventB()); <--- When BlocA is ready, add EventB to BlocB
}
},
child: BlocBuilder<BlocB, StateB>(
builder: (context, state) {
if (state is StateBInitial) {
return Container(color: Colors.yellow); <-- shows initial as expected
}
if (state is StateBSuccess) {
return Container(color: Colors.green); <-- success is never triggered
}
return Container(color: Colors.red);
),
),
),
);
}
In the example above I create 2 BLoCs, BlocA and BlocB.
I add them in providers and then create a BlocListener that listen for BlocA changes and add events to BlocB.
The problem is that the BlocBuilder never triggers after the initial state.
I can see in debugging that the Event added by the the BlocListener is registered and data is fetched and yielded, but the BlocBuilder never triggers even though the state has changed.
It is as if the BlocListener is calling a different BlocB.
If I'm right, you are trying to make communication with two blocs?
There is a really well-explained video tutorial about it: https://www.youtube.com/watch?v=ricBLKHeubM

how do i use provider in this situation

I want to create a change app theme mode and I saw a way of creating it with Provider but I'm new to Provider. For Example, I want to add some codes like this
(the highlighted code)
in my main which consists of many routes
You want to change the theme of the app, then you need to move provider up so it can cover the widget (App in this case) state,
You could do something like this in your main method :
runApp(ChangeNotifierProvider(
create: (context) => ThemeProvider(),
child:MyApp()
);
now in the case of children you could simply call provider in the build method like this
Widget build(){
var themeProvider = Provider.of<ThemeProvider>(context);
}
or you could use the consumer widget
Consumer<ThemeProvider>(
builder: (context, provider, child) {
//return something
}
)
I suggest you to move your ChangeNotifierProvider to your runApp() method
runApp(
ChangeNotifierProvider<ThemeProvider>(
create: (_) => ThemeProvider(),
child: MyApp(),
),
),
Where your MyApp() is just all of your app extracted to its own widget.
Then you can actually easily access it as you wish with a Consumer widget on your build method.
return Consumer<ThemeProvider>(
builder: (BuildContext context, ThemeProvider provider, _) {
return MaterialApp(
theme: provider.myTheme,
...
);
}
)

Flutter: UI reactions with Provider

On some event, I want to navigate to another screen with Navigator.
I could easily achieve it with BlocListener:
BlocListener<BlocA, BlocAState>(
bloc: blocA,
listener: (context, state) {
if (state is Success) {
Navigator.of(context).pushNamed('/details');
}
},
child: Container(),
)
But I can't find the direct equivalent for it in a pure Provider.
The only way I see is to swap screens:
home: Consumer<Auth>(
builder: (_, auth, __) => auth.user == null ? LoginPage() : MainPage()
)
It's a common way. But it will not use Navigator, hence it will just 'pop' MainPage without screen transition.
On some event, I want to play some animation in UI.
I found in the documentation that Listenable class is intended for dealing with Animations, but it's not explained in details.
On some event, I want to clear a TextEditingController.
On some event, I want to show a dialog.
And more similar tasks...
How to solve it? Thanks in advance!
After some research I found a way. I'm not sure if it's the only or the best way, or the way foreseen by Provider's creator, however it works.
The idea is to keep a helper Stream inside of my Store class (I mean business-logic class provided with Provider), and to subscribe to its changes in my widget.
So in my Store class I have:
final _eventStream = StreamController.broadcast();
Stream get eventStream => _eventStream.stream;
void dispose() {
_eventStream.close();
super.dispose();
}
I add events to this stream inside of actions:
void navigateToNextScreen() {
_eventStream.sink.add('nav');
}
void openDialog() {
_eventStream.sink.add('dialog');
}
In my UI widget I have:
#override
void afterFirstLayout(BuildContext context) {
context.read<Transactions>().eventStream.listen((event) {
if (event == 'nav') {
Navigator.push(
context,
MaterialPageRoute(
builder: (ctx) => SecondScreen(),
),
);
} else if (event == 'dialog') {
showDialog(
context: context,
builder: (context) => AlertDialog(content: Text("Meow")));
}
});
}
I used here afterFirstLayout lifecycle method from the after_layout package, which is just a wrapper for WidgetsBinding.instance.addPostFrameCallback
07.07.20 UPD.: Just found a package that can be used for event reactions:
https://pub.dev/packages/event_bus
It basically uses the same approach with StreamController under the hood.

How to use a provider inside of another provider in Flutter

I want to create an app that has an authentication service with different permissions and functions (e.g. messages) depending on the user role.
So I created one Provider for the user and login management and another one for the messages the user can see.
Now, I want to fetch the messages (once) when the user logs in. In Widgets, I can access the Provider via Provider.of<T>(context) and I guess that's a kind of Singleton. But how can I access it from another class (in this case another Provider)?
From version >=4.0.0, we need to do this a little differently from what #updatestage has answered.
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => Auth()),
ChangeNotifierProxyProvider<Auth, Messages>(
update: (context, auth, previousMessages) => Messages(auth),
create: (BuildContext context) => Messages(null),
),
],
child: MaterialApp(
...
),
);
Thanks for your answer. In the meanwhile, I solved it with another solution:
In the main.dart file I now use ChangeNotifierProxyProvider instead of ChangeNotifierProvider for the depending provider:
// main.dart
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => Auth()),
ChangeNotifierProxyProvider<Auth, Messages>(
builder: (context, auth, previousMessages) => Messages(auth),
initialBuilder: (BuildContext context) => Messages(null),
),
],
child: MaterialApp(
...
),
);
Now the Messages provider will be rebuilt when the login state changes and gets passed the Auth Provider:
class Messages extends ChangeNotifier {
final Auth _authProvider;
List<Message> _messages = [];
List<Message> get messages => _messages;
Messages(this._authProvider) {
if (this._authProvider != null) {
if (_authProvider.loggedIn) fetchMessages();
}
}
...
}
Passing another provider in the constructor of the ChangeNotifierProxyProvider may cause you losing the state, in that case you should try the following.
ChangeNotifierProxyProvider<MyModel, MyChangeNotifier>(
create: (_) => MyChangeNotifier(),
update: (_, myModel, myNotifier) => myNotifier
..update(myModel),
);
class MyChangeNotifier with ChangeNotifier {
MyModel _myModel;
void update(MyModel myModel) {
_myModel = myModel;
}
}
It's simple: the first Provider provides an instance of a class, for example: LoginManager. The other Provides MessageFetcher. In MessageFetcher, whatever method you have, just add the Context parameter to it and call it by providing a fresh context.
Perhaps your code could look something like this:
MessageFetcher messageFetcher = Provider.of<ValueNotifier<MessageFetcher>>(context).value;
String message = await messageFetcher.fetchMessage(context);
And in MessageFetcher you can have:
class MessageFetcher {
Future<String> fetchMessage(BuildContext context) {
LoginManager loginManager = Provider.of<ValueNotifier<LoginManager>>(context).value;
loginManager.ensureLoggedIn();
///...
}
}
Seems like this would be a lot easier with Riverpod, especially the idea of passing a parameter into a .family builder to use the provider class as a cookie cutter for many different versions.