is adding ChangeNotifierProvider Widget as Parent of MaterialApp a Flutter Best Practice? - flutter

Can I using the ChangeNotifierProvider on the top of my app tree? is it a good practice in Flutter?
Also running the MyApp Widget as Stateful widget is a good practice?
class _MyAppState extends State<MyApp> {
#override
void initState() {
PushNotificationService.messageStream.listen((message) {
//some code
});
super.initState();
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => MyProvider(),
child: MaterialApp(
//more code
),
);
}
}

Its better to priority to use StatelessWidget in all flutter App. but also when you need to have a initial state, or local state variable, its will be better to use StatefullWidget. So... use as you need.
I would said that using ChangeNotifierProvider on top of tree is a common practice.
see here official example from docs.flutter.dev, they put inside main function.
https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple#changenotifierprovider
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: const MyApp(),
),
);
}
;TLDR
the things that we have to avoid while using provider is with the Consumer at the top of widget tree.
read here : https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple#consumer
It is best practice to put your Consumer widgets as deep in the tree
as possible. You don’t want to rebuild large portions of the UI just
because some detail somewhere changed.
when we called the notifyListeners(); this will rebuild current Consumer widget. when you use it on Top of tree, everytime receive notifylistener your widget Tree will rebuild from the Consumer below.
You can see the example here : https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple#consumer

Related

How ChangeNotifier notify the state?

Can someone give me idea how provider notify the state?
I don't want to use ChangeNotifierProvider, Can you give me a suggestion without library?
I just need better explanation with example.
How provider combine InheritedWidget.
What do you think about the following example (inspired by an answer here) with an AnimatedBuilder:
import 'package:flutter/material.dart';
class MyChangeNotifier extends ChangeNotifier {
int count = 0;
void addOne() {
count++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
final MyChangeNotifier myChangeNotifier = MyChangeNotifier();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: ExampleButton(myChangeNotifier),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: myChangeNotifier.addOne,
),
),
);
}
}
class ExampleButton extends StatelessWidget {
final MyChangeNotifier myChangeNotifier;
const ExampleButton(this.myChangeNotifier, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: myChangeNotifier,
builder: (context, child) {
return OutlinedButton(
onPressed: myChangeNotifier.addOne,
child: Text(
'Tap me - or the floating button\n\n${myChangeNotifier.count}',
textAlign: TextAlign.center,
));
});
}
}
void main() => runApp(MyApp());
The ChangeNotifier implements the Listenable class. You can see here how to listen to that Listenable, for example with an AnimatedBuilder (what my code does).
A ChangeNotifyProvider (I know, you don't want that) would also implement that for you and notify your widgets lower in the widget tree about changes.
Here is some idea for you :
widgets listen to changes and notify each other if there is a rebuild. As soon as the state changes, that particular widget rebuilds without affecting other widgets in the tree.
Three major components make all of this possible: the ChangeNotifier class in Flutter, the ChangeNotifierProvider (primarily used in our sample app), and the Consumer widgets.
Whatever change in the state observed from the ChangeNotifier class causes the listening widget to rebuild. The Provider package offers different types of providers – listed below are some of them:
The Provider class takes a value and exposes it, regardless of the value type
ListenableProvider is the specific provider used for listenable objects. It will listen, then ask widgets depending on it and affected by the state change to rebuild any time the listener is called
ChangeNotifierProvider is similar to ListenableProvider but for ChangeNotifier objects, and calls ChangeNotifier.dispose automatically when needed
ValueListenableProvider listens to a ValueListenable and exposes the value
StreamProvider listens to a stream, exposes the latest value emitted,
and asks widgets dependent on the stream to rebuild FutureProvider
takes a Future class and updates the widgets depending on it when the
future is completed
As a suggestion to learn provider from this article-
https://medium.com/flutter-community/making-sense-all-of-those-flutter-providers-e842e18f45dd

How can I make a card clickable and navigate to another page inside TabBarView?

I'm trying to make a card clickable and navigate to another page 'forecastWeather.dart'. But I can't seem to figure out how I can fix the error I'm getting. The error message says
Another exception was thrown: Navigator operation requested with a context that does not include a Navigator.
I'm using a TabBarView with two tabs and one contains a card which should be clickable and navigate to another page 'forecastWeather.dart'. Please help!
In order to get use navigator.push, you must pass the context of your widget and have a MaterialApp somewhere in your tree, you do both, so the code should work, but it doesn't.
The reason is simple, the context you are passing is outdated, you are first creating the context when the build method begins, at that point, there is no material app, then you add a material app, but the context already exists, so the context you are passing when calling Navigator.push(context ... has no MaterialApp.
A possible fix is to use the Builder widget to make a new context after you create the MaterialApp widget:
#override
Widget build(BuildContext context) {
// context has no material app.
return MaterialApp(
home: Builder(
builder: (context) {
// this context does have a material app and is safe to use.
return DefaultTabController( ... );
}
),
);
}
Another fix is to move the code into a new widget with it's own context:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainPage(),
);
}
}
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
// this context was created after the material app.
return DefaultTabController(...);
}
}
i suggest you to use get: ^4.6.1 package.
with this package you just need to use this for any navigation:
Get.to(ForecastWeather());
this package have so other features read its document here
[1]: https://pub.dev/packages/get

how do you share data between viewmodels in Flutter Provider state management?

I am new to flutter Provider state management. If each view has its own viewmodel, how do you share data between viewmodels?
Lets say you have a viewmodel with a list in it, in one screen you want to show the list, and in another screen you want to show the list count?
You can place ViewModels on the top of widget tree, before MaterialApp
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: <SingleChildWidget>[
ChangeNotifierProvider<ViewModel1>(create: (_) => ViewModel1()),
ChangeNotifierProvider<ViewModel2>(create: (_) => ViewModel2()),
],
child: MaterialApp( ... );
);
}
}
so you can access both viewmodels everywhere

How to have access Context in didChangeAppLifecycleState lifecycle hook using flutter HookWidget?

I'm trying to access context so i can read my provider but since this lifecycle hook is out side the widget tree. it's not accessible. is there a way to get access to context?
I researched a little bit and finally discussed with narcodico from the flutter bloc community, so the credits are for him.
Therefore, mixin WidgetsBindingObserver on a state class, the context is available even in the overrides like didChangeAppLifecycleState since they are part of the state class.
Also, take in consideration to move to BlocProvider above the state widget.
Example
class HomePageProvider extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<InAppPurchasesBloc>(),
child: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomePageState();
}
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
...
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
context
.read<InAppPurchasesBloc>()
.add(const InAppPurchasesEvent.getPurchaserInfo());
}
}
...
}
I am afraid you can't access context inside didChangeAppLifecycleState.
For anyone interested, you can save your scaffold state in a global key, and access the context from its current state.
You can use useEffect function, read more:
https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useEffect.html;
Widget build(BuildContext context) {
useEffect(() {
//what would you write in initState
},
);
You may consider using the Riverpod package instead of Provider. Riverpod is from the same author as Provider and considered the "better Provider", but with many improvements including Flutter independence, meaning it does not rely on a context to work, and you can use it almost the same way as provider.
Using Riverpod, along with Flutter Hooks, you can do something like:
// create a provider in a global context
final myProvider = Provider((ref) => myClass());
// access the provider inside your class
class MyWidget extends HookWidget{
//access the provider using a hook
final myClassProvider = useProvider(myProvider);
//... your logic
#override
Widget build (BuildContext context){/* ... build widget tree... */}
}
Consider this very useful and concise tutorial with how to use Riverpod with Flutter Hooks and StateNotifier, ChangeNotifier, etc...

Recommendation when using bloc pattern in flutter

When using flutter bloc what is the recommendation, is it recomended for each page to have its own bloc or can i reuse one block for multiple pages, if so how?
I think that the best solution is to have one BLoC per page. It helps you to always know in which state each screen is just by looking at its BLoC. If you want to show the state of each of your tabs independently you should create one BLoC for each tab, and create one Repository which will handle fetching the data. But if the state of every tab will be the same, (for example you fetch data only once for all of the screens, so you don't show loading screen on every tab) then I think that you could create just one BLoC for all of this tabs.
It is also worth to add, that BLoCs can communicate with each other. So you can get state of one BLoC from another, or listen to its state changes. That could be helpful when you decide to create separate BLoCs for tabs.
I have addressed this topic in my latest article. You can check it out if you want to dive deeper.
There are no hard-set rules about this. It depends on what you want to accomplish.
An example: if each page is "radically" from each other, then yes, a BLoC per page makes sense. You can still share an "application-wide" BLoC between those pages if some kind of sharing or interaction is required between the pages.
In general, I've noticed that usually a BLoC "per page" is useful as there are always specific things related for each page that you handle within their BLoC. You can the use a general BLoC to share data or some other common thing between them.
You can combine the BLoC pattern with RxDart to handle somewhat more complex interaction scenarios between a BLoC and the UI.
Sharing a BLoC is fairly simple, just nest them or use a MultiProvider (from the provider package):
runApp(
BlocProvider(
builder: (_) => SettingsBloc(),
child: BlocProvider(
builder: (_) => ApplicationBloc(),
child: MyApp()
)
)
);
and then you can just retrieve them via the Provider:
class MyApp extends ... {
#override
Widget build(BuildContext context) {
final settingsBloc = Provider.of<SettingsBloc>(context);
final appBloc = Provider.of<ApplicationBloc>(context);
// do something with the above BLoCs
}
}
You can share different bloc's in different pages using BlocProvider.
Let's define some RootWidget that will be responsible for holding all Bloc's.
class RootPage extends StatefulWidget {
#override
_RootPageState createState() => _RootPageState();
}
class _RootPageState extends State<RootPage> {
NavigationBloc _navigationBloc;
ProfileBloc _profileBloc;
ThingBloc _thingBloc;
#override
void initState(){
_navigationBloc = NavigationBloc();
_thingBloc = ThingBloc();
_profileBloc = ProfileBloc();
super.initState();
}
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<NavigationBloc>(
builder: (BuildContext context) => _navigationBloc
),
BlocProvider<ProfileBloc>(
builder: (BuildContext context) => _profileBloc
),
BlocProvider<ThingBloc>(
builder: (BuildContext context) => _thingBloc
),
],
child: BlocBuilder(
bloc: _navigationBloc,
builder: (context, state){
if (state is DrawProfilePage){
return ProfilePage();
} else if (state is DrawThingsPage){
return ThingsPage();
} else {
return null
}
}
)
)
}
}
And after that, we can use any of bloc from parent and all widgets will share the same state and can dispatch event on the same bloc
class ThingsPage extends StatefulWidget {
#override
_ThingsPageState createState() => _ThingsPageState();
}
class _ThingsPageState extends State<ThingsPage> {
#override
void initState(){
_profileBloc = BlocProvider.of<ProfileBloc>(context);
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
child: BlocBuilder(
bloc: _profileBloc,
builder: (context, state){
if (state is ThingsAreUpdated){
return Container(
Text(state.count.toList())
);
} else {
return Container()
}
}
)
);
}
}