How to read StateNotifierProvider state inside FutureProvider - flutter

I have a StateNotifierProvider which has getter and setter method of a class variable.
I'm trying to access the class variable value via getter method inside a FutureProvider
I've tried multiple ways to access the value inside the FutureProvider but unable to get any success.
I am getting the below error
errror snapshot
Let me know the correct approach.
class SelectedPost extends StateNotifier<String> {
SelectedPost() : super("");
var uuid;
void setPostId(String id) {
uuid = id;
}
String getPostId() {
return uuid;
}
}
Here I'm trying to access the getter method to fetch uuid value
final selectedPostProvider = StateNotifierProvider((ref) => new SelectedPost());
final detailPostFuture = FutureProvider<Post>((ref) async {
final _selectedPostProvider = ref.watch(selectedPostProvider);
return fetchDetailPost(_selectedPostProvider.getPostId());
});

If you want to use the state itself, use this.
final detailPostFuture = FutureProvider<Post>((ref) async {
final _selectedPostProvider = ref.watch(selectedPostProvider);
});
If you want to access only the members (like functions, variables), use this.
final detailPostFuture = FutureProvider<Post>((ref) async {
final _selectedPostProvider = ref.watch(selectedPostProvider.notifier);
});
If you want to use both, use this.
final detailPostFuture = FutureProvider<Post>((ref) async {
final _selectedPostState = ref.watch(selectedPostProvider);
final _selectedPostProvider = ref.watch(selectedPostProvider.notifier);
});
See this migration doc for reference.

You need to watch the Notifier itself. It can be done by using:
final _selectedPostNotifier = ref.watch(selectedPostProvider.notifier);

Related

flutter X firebase: how to correctly initialize fields with data in different branches in firebase realtime database

I have a piece of code trying to initialize the fields with data from two different users in the firebase realtime database, I tried various ways but all of them don't work and the field is not initialized error keeps popping up.
Here is the code:
class _PartnerProfilePageState extends State<PartnerProfilePage> {
final userUID = FirebaseAuth.instance.currentUser!.uid;
final database = FirebaseDatabase.instance.ref();
late final partnerUID;
late final profilePath;
late final partnerName;
late final birthday;
late final en;
#override
initState() {
super.initState();
initInfo();
}
initInfo() async {
database.child(userUID).onValue.listen((event) {
final data = Map<dynamic, dynamic>.from(event.snapshot.value as Map);
setState(() {
partnerUID = data['partner'];
en = data['language'].toString().startsWith('en');
initPartnerInfo();
});
});
}
Future initPartnerInfo() async {
final ppsnapshot =
await database.child(partnerUID).child('profilePath').get();
profilePath = ppsnapshot.value.toString();
final nsnapshot = await database.child(partnerUID).child('username').get();
partnerName = nsnapshot.value.toString();
final bsnapshot = await database.child(partnerUID).child('birtday').get();
birthday = bsnapshot.value.toString();
}
//rest of other unrelated stuff like build down there
}
(My firebase realtime database has no 'user' branch but directly save every user in the root with their userid).
I think there is a problem with the async initializing. The build method can try to build before you initialize the last variable(Because initState method can not async). You can easily check my theory.Delete the 'method call'(the initInfo) inside the initState(). Just create a button in the screen, give it a anonymous async function, inside the function call your init method and try to call your variables like this:
() async{
await initInfo();
print('partnerUID');
}
Easy way to checking out initialization process. Hope it helps.

Read a provider inside a FutureProvider

When we need to read (not watch) a provider inside another one the documentation is clear:
"DON'T CALL READ INSIDE THE BODY OF A PROVIDER"
final myProvider = Provider((ref) {
// Bad practice to call `read` here
final value = ref.read(anotherProvider);
});
And it suggest to pass to the value exposed the Reader function: https://riverpod.dev/docs/concepts/combining_providers#can-i-read-a-provider-without-listening-to-it
final userTokenProvider = StateProvider<String>((ref) => null);
final repositoryProvider = Provider((ref) => Repository(ref.read));
class Repository {
Repository(this.read);
/// The `ref.read` function
final Reader read;
Future<Catalog> fetchCatalog() async {
String token = read(userTokenProvider);
final response = await dio.get('/path', queryParameters: {
'token': token,
});
return Catalog.fromJson(response.data);
}
}
And that's ok, but what is the best practice when I need to read a provider inside a FutureProvider?
I find myself in this situation many time because I expose the api as providers and inside the FutureProvider I call watch to get the api I need.
But I noticed that, because I'm watching the Api provider inside the userProvider, this won't gets disposed after been used.
Here's an example of what I'm trying to say:
API CLASS
final userApiProvider = Provider((ref) => UserApi(ref.read));
class UserApi {
final Dio _dio;
const UserApi(Reader read):
_dio = read(dioProvider);
Future<Response> getUser(String id, { CancelToken? cancelToken }) async{
final _url = '$URL_TO_API/$id';
return _dio.get(_url, cancelToken: cancelToken);
}
}
When using the API inside a FutureProvider
final userProvider = FutureProvider.autoDispose.family<User, int>((ref, userId) async {
final userApi = **ref.watch(userApi);**
final cancelToken = CancelToken();
ref.onDispose(() { cancelToken.cancel(); });
final user = await userApi.getUser(cancelToken: cancelToken);
return user;
});
The same logic applies.
By "Don't use read inside a provider", it isn't talking about the class Provider specifically, but any provider – so FutureProvider included.
In general, you should avoid using read as much as possible.

How can i always setState when the page is opened?

My issue is, when the page is opened my variable isn't set. So when i'm querying from firestore i get the error The method '[]' was called on null. Receiver: null Tried calling: []("userbio")
I declared 2 variables
var userid;
var useremail;
Then i wrote this future
Future getUserData() async {
var firestore = Firestore.instance;
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
final String uid = user.uid.toString();
final String email = user.email.toString();
DocumentReference qn = await firestore
.collection("users")
.document(email)
.collection('userdetails')
.document(uid);
void initState() {
setState(() {
userid = uid;
useremail = email;
});
}
return qn.documentID;
}
This is how i'm currently trying to setState but it's not working
void initState() {
setState(() {
userid = uid;
useremail = email;
});
}
Please help, thanks.
comment if you need more context
Seems like you're declaring the nested function initState() but you're not calling it. I would just remove the function and call setState() directly :
...
.document(uid);
setState(() {
...
EDIT : Call getUserData() inside initState :
// In a class extending State<T>
#override
void initState() {
super.initState();
// synchronous call if you don't care about the result
getUserData();
// anonymous function if you want the result
() async {
var result = await getUserData();
}();
}
PS : initState(), like build() or setState(), is a predefined name in Flutter. Try not to use those names for functions that are not overriding the original ones.
Try assign your variable an empty value instead of leaving it null.
var userid = '';
var useremail = '';

How do I initialize a dart class's private property with an async value without initstate?

Every time this class is instantiated, I wish a call to be made to firebase and the _datapoint given the retrieved value. Otherwise, I have to assign the datapoint in each function in the class (see functionOneExample), and that's just prone to errors. Note: I cannot use initstate as this function is not a widget (I do not need or want a build method). If I could, I would call _getThis in the initstate. Thanks for your help!!
class AsyncInitExample {
AsyncInitExample(this.enterThis);
String enterThis;
String _datapoint;
_getThis() async {
var firebaseRetrieved = await //Firebase get this;
this._datapoint = firebaseRetrieved;
}
Future<dynamic> functionOneExample {
this._datapoint ?? await _getThis();
}
// etc. etc. etc.
}
I can recommend making a private constructor together with a static method to fetch all the async values and then use the private constructor to return a object:
class AsyncInitExample {
AsyncInitExample._(this.enterThis, this._datapoint);
String enterThis;
String _datapoint;
static Future<AsyncInitExample> getInstance(String enterThis) async {
var firebaseRetrieved = await //Firebase get this;
return AsyncInitExample._(enterThis, firebaseRetrieved);
}
String functionOneExample() => _datapoint;
// etc. etc. etc.
}
By doing it this way, you just need to await the Future from getInstance() and after this, you can access all variables in the class without awaiting.

Is there any way we can inject provider dependancy to normal class in flutter?

I have one normal dart class in which I want to provide two provider dependency.
So I can access that class though out of my application. I can pass that dependency from the build method of the widget and then I can use this class but I don't want to do that, like 100 times I have to pass that dependency if I used that class 100 times.
I also want to use this class from every lifecycle of flutter as it's generating different types of events for application.
I also want to initialize genrateUserProerties() method only once when the dependency is ready and when the user opens the application.
This is initialized before any provider initializes and it's not always used from the method where we have context available.
I need a way to provide that dependency in a way we can initialize genrateUserProerties() only once.
User _user; and BrandCofiguration _activeBrand; I need these two be pass here when it's ready.
User _user; and BrandCofiguration _activeBrand; both are coming from two different Providers when I received a valid response from the server.
class FireBaseAnalyticsBase {
static FirebaseAnalytics _analytics;
static FirebaseAnalyticsObserver _observer;
**User _user;**
BuildContext _context;
**BrandCofiguration _activeBrand;**
int _seconds;
Stopwatch _stopwatch;
String _eventName;
Map<String, dynamic> _userProperties = {};
bool _isTimeTrackEvent;
FireBaseAnalyticsBase(BuildContext context, UserProvider userProvider,
BrandSelectionProvider brandSelectionProvider) {
this._context = context;
_analytics = FirebaseAnalytics();
_observer = FirebaseAnalyticsObserver(analytics: _analytics);
_activeBrand = brandSelectionProvider.activeBrand;
_user = userProvider.authenticatedUser;
if (_user != null) {
genrateUserProerties();
}
}
void startFirebaseEventWithoutTime(String eventName) {
this._eventName = eventName;
_isTimeTrackEvent = false;
logFirebaseEvent();
}
void startFireBaseEventWithTime(String eventName) {
_stopwatch = Stopwatch();
_stopwatch.start();
_isTimeTrackEvent = true;
_eventName = eventName;
}
void stopFireBaseTimeEvent() {
_stopwatch.stop();
_seconds = (_stopwatch.elapsedMilliseconds / 1000) as int;
_stopwatch.reset();
logFirebaseEvent();
}
Future<void> logFirebaseEvent() async {
if (_isTimeTrackEvent) {
_userProperties
.addAll({FirebaseAnalyticsEnum.time_spent.value: _seconds});
}
print("firebase test");
await _analytics.logEvent(
name: _eventName,
parameters: _userProperties,
);
}
Future<void> genrateUserProerties() async {
print("firebase properties initilize");
var _packageInfo = await PackageInfo.fromPlatform();
_userProperties = {
FirebaseAnalyticsEnum.user_id.value: _user.id.toString(),
FirebaseAnalyticsEnum.platform.value: Platform.operatingSystem,
FirebaseAnalyticsEnum.device_language.value:
Localizations.localeOf(_context).languageCode,
FirebaseAnalyticsEnum.application.value: _packageInfo.appName,
FirebaseAnalyticsEnum.current_api.value: Config.CURRENT_API,
FirebaseAnalyticsEnum.device_type.value: _user.id.toString(),
FirebaseAnalyticsEnum.app_version.value: Config.CURRENT_VERSION,
FirebaseAnalyticsEnum.is_admin.value: _user.isAdmin,
FirebaseAnalyticsEnum.is_educator.value: _user.educator,
FirebaseAnalyticsEnum.is_brand_ambassador.value: _user.brandAmbassador,
FirebaseAnalyticsEnum.salon_role.value: _user.salongroup,
FirebaseAnalyticsEnum.brand.value: _activeBrand.brandName,
FirebaseAnalyticsEnum.school_role.value: _user.schoolgroup,
};
}
}
I think you should use Singleton pattern for these classes consume a lot of resources.
Example:
static FireBaseAnalyticsBase _instance;
static FireBaseAnalyticsBase getInstance(BuildContext context, UserProvider userProvider,
BrandSelectionProvider brandSelectionProvider){
if(_instance == null){
_instance = FireBaseAnalyticsBase(context,userProvider,brandSelectionProvider);
}
return _instance;
}
Or if you passing BuildContext you can get Provider
Provider.of<>(context) in FireBaseAnalyticsBase's Constructor