How to use a provider insider another provider without using context in Flutter - flutter

I'm using Flutter provider, and I want to access a provider from another provider without using context.
I found many documentations on the internet explaining how to use ProxyProvider, but no one of the implementations was working (I think it's related to the last update 3 months ago).
I posted this question in StackOverflow after encountering a problem in ProxyProvider, but I didn't get an answer.
So now I'm just searching for any way to use a provider inside another one without using context.

You mean a Provider or the service that the Provider encapsulates? If it's the latter, the way that I've done it is by calling a provided service inside another provided service, given that they are all under the same MultiProvider. For example, I have two provided services:
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => FirstService()
),
ChangeNotifierProvider(
create: (_) => SecondService()
),
]
)
FirstService looks like this:
class FirstService extends ChangeNotifier {
List<String> getListOfValues() {
return ['a', 'b', 'c'];
}
}
and for example, I need to access those values from the SecondService, then I do this:
class SecondService extends ChangeNotifier {
void pullDataFromService(BuildContext context) {
FirstService firstService = Provider.of<FirstService>(context, listen: false);
var values = firstService.getListOfValues();
}
}
So pretty much you pass a context to a method where you need to access a provided service and you extract it from the Provider as normal, then access its functionality from the other service where you fetch it.

Related

How to migrate Provider to Riverpod?

I got this error:
'ChangeNotifierProvider' isn't a function. Try correcting the name to
match an existing function, or define a method or function named
'ChangeNotifierProvider'
I'm looking to migrate provider to Riverpod, but it is difficult for me to do that
This is my code:
void main() async {
await Hive.initFlutter();
await Hive.openBox("Habit_Database");
runApp(MultiProvider(providers: [
ChangeNotifierProvider(
create: (context) => UserProvider(),
),
], child: const MyApp()));
}
How can I solve this issue?
Thanks for any help you can provide
I'm working on this right now for a client. I have chosen to maintain the existing provider infrastructure while slowly migrating one provider at a time to riverpod.
To make this work, I first constructed a renaming shim for all of the exported provider functions. It imports provider, and establishes typedefs to alias those names to universally end in "X", including the extension on BlockContext which becomes .readX and .selectX. I tested this by not having the "X" initially, then renaming each symbol in VSC one at a time, which surprisingly worked well. The renaming shim looks something like:
import 'package:provider/provider.dart' as provider;
typedef ProviderX<T> = provider.Provider<T>;
typedef MultiProviderX = provider.MultiProvider;
typedef ChangeNotifierProviderX<T extends ChangeNotifier> = provider.ChangeNotifierProvider<T>;
which continues for about 100 lines. The tricky ones are the extensions:
extension ReadContext on BuildContext {
T readX<T>() => provider.ReadContext(this).read<T>();
}
extension SelectContext on BuildContext {
R selectX<T, R>(R Function(T value) selector) => provider.SelectContext(this).select(selector);
}
Admittedly, once I started the pattern, Github copilot eagerly offered me line after line, and was wrong for only a few things initially.
Next, I added the RiverPod ProviderScope to my runApp, and selected a particular provider to migrate. I created the equivalent in RiverPod, nicely namespaced because "FooProvider" became "fooProvider", and then located all references to that class in .readX or ConsumerX access. I inserted the equivalent with Consumer blocks or ConsumerWidget widgets, giving me a ref to use with ref.read or ref.watch as appropriate.
It's not trivial. But once you get over "the great rename" hurdle, the rest is just a rather mechanical translation, and can be done incrementally.
The error message is indicating that ChangeNotifierProvider isn't a function. You are trying to migrate from provider to Riverpod.
In Riverpod, the equivalent class for ChangeNotifierProvider is ChangeNotifierProvider.autoDispose.
So you should replace this line:
ChangeNotifierProvider(
create: (context) =>
UserProvider(),
),
with this line:
ChangeNotifierProvider.autoDispose( create: (context) => UserProvider(),),
This should solve the issue.

Flutter: accessing providers from other providers

For my flutter project, I am using the following multiple providers below:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<FirstProvider>(
create: (context) => FirstProvider(),
),
ChangeNotifierProvider<SecondProvider>(
create: (context) => SecondProvider(),
),
ChangeNotifierProvider<ThirdProvider>(
create: (context) => ThirdProvider(),
),
ChangeNotifierProvider<FourthProvider>(
create: (context) => FourthProvider(),
),
],
child: const MainApp(),
);
}
Because sometimes I need to either get data or call functions from different providers from another provider, I am using it like this:
//First Provider
class FirstProvider with ChangeNotifier {
void callFunctionFromSecondProvider({
required BuildContext context,
}) {
//Access the SecondProvider
final secondProvider= Provider.of<SecondProvider>(
context,
listen: false,
);
secondProvider.myFunction();
}
}
//Second Provider
class SecondProvider with ChangeNotifier {
bool _currentValue = true;
void myFunction(){
//Do something
}
}
The callFunctionFromSecondProvider()of the FirstProvider is called from a widget and it will call myFunction() successfully, most of times.
Depending on the complexity of the function, I am sometimes experiencing that I can't access the SecondProvider, presumably due to context being null, when the widget state changes.
I am reading some documents online regarding provider, and they are suggesting changenotifierproxyprovider for what I understood as 1 to 1 provider relationship.
However, in my case, one provider needs to be accessed by multiple providers and vice versa.
Question:
Is there a more appropriate way that I can approach my case where one provider can be accessed by multiple providers?
EDIT:
Accessing provider should also be able to access different variable values without creating a new instance.
Instead of passing context to the callFunctionFromSecondProvider function add the second provider as the parameter. So the function looks like the below.
Not sure this is the correct way of doing that but my context null issue was fixed this way.
void callFunctionFromSecondProvider({
required SecondProvider secondProvider,
}) {
secondProvider.myFunction();
}
}
Alright.
So it looks like Riverpod by the same author is the way to go as it addresses alot of flaws such as Provider being dependent on the widget tree, in my case, where the underlying issue came from.
—--------
For the time being, I still need to use the provider and for a quick and dirty solution, I am providing the context of not only the current widget that I am trying to access the provider, but also passing the parent context of the widget directly, so that in case a modal (for example) is closed, then any subsequent provider call can still be executed using the parent context.
Hope this helps.

ChangeNotifierProxyProvider not initiated on build

I'm trying to understand multiproviders in Flutter. In my App, one Provider need to change based on a value from an other Provider.
AuthProvider gets initiated higher up in the widget tree on build. Works like a charm with automatic sign in if possible...
In a lower placed widget, I try to initiate two other Providers. One, WhatEver, is not depended on other data and gets initiated on build like it is supposed to using ChangeNotifierProvider.
ProductList however is depended on AuthProvider. If log in status is changed, the ProducList should update accordingly.
In my attempts, I've found out, ie found on SO, that ChangeNotifierProxyProvider is the right way to go. But when I run the App, it seems like the 'create'-part of ChangeNotifierProxyProvider is not initiated when the widget gets build. It seems like the ProductList provider is not initiated until it's read or written to.
What have I misunderstood using MultiProviders and ChangeNotifierProxyProvider?
return MultiProvider(
providers: [
ChangeNotifierProvider<WhatEver>(create: (context) => WhatEver()),
ChangeNotifierProxyProvider<AuthProvider, ProductList>(
create: (_) => ProductList(Provider.of<AuthProvider>(context, listen: false)),
update: (_, auth, productList) => productList..reloadList(auth)
),
],
The ProductList looks like this:
final AuthProvider _authProvider;
static const String _TAG = "Shop - product_list.dart : ";
ProductList(this._authProvider) {
print(_TAG + "ProductList Provider initiated");
reloadList(this._authProvider);
}
void reloadList(AuthProvider authProvider) {
print(_TAG + "ProductList reload started");
if (authProvider.user==null) {
print(_TAG + "ProductList: _authProvider == null");
_loadBuiltInList();
} else {
print(_TAG + "ProductList: user = " + authProvider.user.displayName);
_loadFirestoreList();
}
}
I have code that does this:
ChangeNotifierProxyProvider<AuthService, ProfileService>(
create: (ctx) => ProfileService(),
update: (ctx, authService, profileService) =>
profileService..updateAuth(authService),
),
My ProfileService() does not rely on AuthService being available when it is constructed. The code works fine :)
The ChangeNotifierProxyProvider documentation explicitly describes this approach:
Notice how MyChangeNotifier doesn't receive MyModel in its constructor
anymore. It is now passed through a custom setter/method instead.

Flutter Better way to add Providers in main.dart file

In main.dart file I always have to add the same thing with different class name to make things works, here is an example.
MultiProvider(
providers: [
ChangeNotifierProvider<ProductDataProvider>(
create: (_) => ProductDataProvider()),
ChangeNotifierProvider<AuthenticationProvider>(
create: (_) => AuthenticationProvider()),
],
child: Container())
If we have 20 providers let's say then there is a lot duplicate code there right. Is any work around this?
See, if it is about initializing your provider in your main.dart, I am afraid, you have to do it, cos it need those.
For any duplicates, you can make use of some short tricks and get going.
Create an Array consisting of all your ChangeNotifiers, like in this case: ProductDataProvider and AuthenticationProvider
List<ChangeNotifier>_providersArray = [ProductDataProvider, AuthenticationProvider];
Now, when you have the array, add it to the array which adds the ChangeNotifier, to your final providers list.
// This will be your array of Providers, which you will add to Mutliprovider(providers: HERE)
List<Provider> providers = []; // make sure you have the right type of the List<>, which the `providers` in `Multiproviders` accepts
for(var provider in _providersArray){
//adding the provider name to the ChangeNotifier
providers.add(ChangeNotifierProvider<provider>( create: (_) => provider()));
}
Finally passing the providers in your Multiprovider
MultiProvider(
providers: providers,
child: Container()
)
Max to max, you will have to do the type casting for some type mismatches, and you're good to go. Let me know if that helps you in anyway.

Provider not accesable when Navigate to new screen

have a problem that I'm sitting on couple of days now.
have an app where:
depending of AUTH state, 'LoginScreen' or 'MainScreen' is Shown.
in MainScreen I setUp bottomNavigation with screens (HomeScreen, ShoppingScreen,MyFavorites)
I set up there as well my StreamProviders(those depend on Auth) by using MultiProvider
on HomeScreen when I User Provider.of(context) it works like it should
but when I use :
`Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ProfileScreen(),
),
);
` and use Provider.of(context) there I get "Could not find correct Provider....above this...widget"
I read some issues on that and solution there was to decler providers above MaterailApp which in my case I can not do because I can set up thoese only after Auth is successfull.
Tryed passing context(from HomeScreen) to ProfileScreen(through constructor) and that work but when value changed of UserData it did not update the screen (guessing beacause of diffrent 'contexts')
What am I doing wrong in here,any Ideas?:S
Providers are "scoped".
This means that if they are placed inside a screen, they aren't accessible outside that screen.
Which means that if a provider is scoped but needs to be accessed outside of the route it was created in, we have two solutions:
un-scope the provider. This involves moving the provider to a common ancestor of both widgets that needs to obtain the value.
If those two widgets are on two different Routes, then it basically mean "move the provider above MaterialApp/CupertinoApp.
manually pass the provider to the new screen (needed when using Navigator.push)
The idea is, instead of having one provider, we have two of them, both using the same value as explained here See How to scope a ChangeNotifier to some routes using Provider? for a practical example.
For Navigator.push, this can look like:
final myModel = Provider.of<MyModel>(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
ChangeNotifierProvider.value(
value: myModel,
child: MyScreen(),
),
),
);
Please make sure that you application's root widget is Provider Widget, it should event be the parent of MaterialWidget. If this is already the case I will need your code to look into. Something like this
class AppState {
User loggedInUser;
bool get isLoggedIn {
return loggedInUser != null;
}
// Other states as per the requirements
// ...
}