how do i use provider in this situation - flutter

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,
...
);
}
)

Related

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

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.

is it okay to have two material apps in one flutter?

I added two material app to my app because my futurebuilder needed a context and my provider was not accessible to the other classes i created. is it an acceptable practice???
runApp(
MaterialApp(
title: 'register app',
home: FutureBuilder(
future: Hive.openBox('store'),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError)
return Text(snapshot.error.toString());
else
return MultiProvider(providers: [
ChangeNotifierProvider.value(
value: form_entry(),
)
], child: MyApp());
} else
return Scaffold(
body: Center(
child: Text('error error ooops error'),
));
},
)),
);[enter image description here][1]
// my app class has another material app
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
home: home_screen(),
);
}
}
The purpose of a MaterialApp widget is to provide a common theme setting based on Material design and configures the root navigator for all of its children widgets.
In order to avoid conflicting, you should only have 1 MaterialApp. In your case, you can call the openBox() method without using the FutureBuilder by calling it within the main() method:
void main() async {
// Include this line to make sure WidgetsFlutterBinding is initialized, since
// we're using main() asynchronously
WidgetsFlutterBinding.ensureInitialized();
// Open the Hive box here
var box = await Hive.openBox('store');
// Then run the app
runApp(
MaterialApp(
title: 'register app',
home: MultiProvider(providers: [
ChangeNotifierProvider.value(
value: form_entry(),
)
], child: home_screen());
)
);
Small note: When creating new class or method in Dart, best practice is to use CamelCase. So form_entry() should be named FormEntry() for Class name or formEntry() for Method name. Same goes with home_screen(). You can refer to the styling guide here
It's bad to have two MaterialApp() widgets, at least one in other.
I did that by mistake, I thought I do not have one and added an extra one. Then the app randomly crashed on hot restart, sit one whole day to debug everything and haven't found what crashed my app, then I started to refactor code and found I have two MaterialAapp, one in StatelessWidget and one in home widget that was on different file. After removing it my app stopped randomly crashing.
Never use two, atleast not one in other.

Advantage from static routing to dynamic routing on Flutter

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);

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.

How to write the syntax for onTap() event to direct to other/new pages dynamically in a dynamic ListView in flutter?

I have created a code in which multiple ListTile widgets are created dynamically based on the list of items i store. I want to add onTap() functionality to it so that it redirects to the respective pages i have created.
Here i need how to redirect to a particular page based on the onTap() which is contained in a list view.
from the above code i don't know how to direct to a respective page based on the onTap() event.
I can suggest the following:
1. Create a class to handle only the routes of your application, as you can see below:
class Router {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => HomeScreen());
case 'screen_1':
return MaterialPageRoute(builder: (_) => Screen1());
case 'screen_x':
return MaterialPageRoute(builder: (_) => ScreenX());
default:
return MaterialPageRoute(builder: (_) {
return Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}'),
),
);
});
}}}
Use the onGenerateRoute property of the MaterialApp widget, to tell you to call that generator when the app tries to navigate to a named path, also you can use the initialRoute property if you want:
MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: '/',
onGenerateRoute: Router.generateRoute,
)
In the dynamic list you are generating, use the following:
Navigator.pushNamed (context, "screen_x");
where "screen_x" can be a property of the dynamic list object.