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(),
),
}
Related
So, I'm making an app of OTP login with flutter and dart and when I provide the pohone number it's shows an error
The code is this:
void sendPhoneNumber() {
final ap = Provider.of<AuthProvider>(context, listen: false);
String phoneNumber = phoneController.text.trim();
ap.signInWithPhone(context, "+{selectedCountry.phoneCode}$phoneNumber");
}
The error it's when I'm in final ap, there shows this: "Tried to listen to a value exposed with provider, from outside of the widget tree."
Any idea what can i do to solve it?
Seem like you're forgetting to create an object
Single
Provider(
create: (_) => MyModel(),
child: ...
)
Multi
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => myViewModel()),
]
)
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.
I can see that MaterialApp app can receive routes.
1. Static routing
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/page1': (context) => Page1(title: "Main page"),
...
And show them from the widgets like:
myKey.currentState.pushNamed("/page1");
There are other parameters like onGenerateRoute and initialRoute which confuse me more.
2. Dynamic Pages
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
}
Question
Im wondering what is the implication of this parameters and letting this "responsibility" to the MaterialApp, and why we should do it, maybe something related to memory management or how the Widget lifecycle works, or what?
What are the differences between 1. and 2.?
The answer lies more in your architecture than anything.
1. Static Routing is the better of the two in terms of managing a projects complexity. Routes are clearly defined for multiple developers to understand, and the navigation code is much easier, Navigator.of(context).pushNamed('your-route'); vs
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
2. Dynamic Pages is commonly in tutorials and such to reduce boilerplate code. It is merely a shortcut to navigate. The downside of this is it becomes hard to manage routes, and so should be limited to short tutorials.
3. Generated Routes There is a third option though, that in my opinion is the best of the two, and that is a Generated Routes. This is the cleanest and easiest to mantain structure. There is a great tutorial here about it. Here is the rundown:
Declare Routes:
class RoutePaths {
static const Start = '/';
static const SecondScreen = 'second'
}
Declare your router:
class Router {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case RoutePaths.Start:
return MaterialPageRoute(builder: (_) => YourFirstScreenWidget());
case RoutePaths.SecondScreen:
// you can do things like pass arguments to screens
final event = settings.arguments as Event;
return MaterialPageRoute(
builder: (_) => YourSecondScreenWidget(event: event));
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}'),
),
));
}
}
}
Declare it in main.dart
initialRoute: RoutePaths.Start,
onGenerateRoute: Router.generateRoute,
Navigate
// arguments: event is an optional parameter to send to secondScreen
Navigator.of(context).pushNamed(RoutePaths.SecondScreen, arguments: event);
New to Flutter, I have Provider on top of my app with the class Events. Is there any way to inject more than one object in Navigator builder like MapBox(events.itmaps, events.maps) for example?
class Events {
final String site, fb, itmaps, maps;
Events({this.site, this.fb, this.itmaps, this.maps});
}
void main() {
final events = Events();
runApp(
Provider<Events>.value(
value: events,
child: MyApp(),
),
);
}
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => MapBox(events.itmaps),
),
);
}
As I understood you have some conceptual misunderstandings!. I'll describe two scenarios, hopefully one of them will fit to your requirement.
Using MultiProvider to inject many Dependencies(Classes/Objects/Stores)
As https://pub.dev/packages/provider described it would be like this:
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: someWidget,
)
Passing arguments/props to Widgets
Despite the descriptions and keywords you used, by looking at your code I can guess you want to pass a second or more input/arguments/props to your screen widget. Every widget input is a class constructor argument. So you need just declare the desire parameters in the constructor of your MapBox class.
class MapBox extends StatelessWidget {
EventModel firstInput;
OtherEventModel secondInput;
MapBox(this.firstInput, this.secondInput);
.
.
.
}
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.