Flutter Riverpod/Provider: Setting an initial state for a stateNotifier - flutter

So I want to have a provider who's initial state will be the a user's location, this is what I have now, is this right?
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/all.dart';
import 'package:location/location.dart';
class MapApiState {
final LocationData userLocation;
MapApiState({#required this.userLocation});
}
class MapNotifier extends StateNotifier<MapApiState> {
static Location _location = Location();
MapNotifier() : super(null) {
_location.getLocation().then((initialState) =>
super.state = MapApiState(userLocation: initialState));
}
}
Is this the right approach? How do I now declare the global provider variable for this? The super(null) worries me. But I can't just put "super(_location.getLocation())"
Also, how should I then instantiate my global provider variable?

Well it almost depends of how you want to apply it in the lifecycle of your app, you can use StreamProvider or FutureProvider to get the location and then pass it to your StateNotifier
final locationProvider = FutureProvider<LocationData>((ref) {
final Location _location = Location();
return _location.getLocation();
});
final singleDetailDealProvider = StateNotifierProvider<MapNotifier>((ref) {
final location = ref.watch(locationProvider);
return MapNotifier(location.data?.value);
});
class MapNotifier extends StateNotifier<MapApiState> {
MapNotifier(LocationData location) : super(location);
}
That way if you want to use the location somewhere else you can call locationProvider without depending on the state of MapNotifier

Related

Combining Riverpod Providers Bidirectionally

How can we access a method from the being wrapped riverpod provider?
ContentProvider can access user value from UserProvider by using "watch". There is no problem for this direction. On the other hand, UserProvider also needs access to the methods of ContentProvider. So bidirectional communication is required.
For this case, I need to call deleteContents method from UserProvider.
I don't prefer to merge them to keep logic safe.
class ContentProviderNotifier extends ChangeNotifier {
final User? currentUser;
ContentProviderNotifier({required this.currentUser});
addContent(Content content) {
content.user = currentUser?.name;
...
}
deleteContents() {
...
}
}
final contentProvider = ChangeNotifierProvider<ContentProviderNotifier>(
(ref) {
final user = ref.watch(userProvider).currentUser;
return ContentProviderNotifier(currentUser: user);
},
);
class UserProviderNotifier extends ChangeNotifier {
UserProviderNotifier();
User? currentUser;
deleteUsers(){
// here to call a method from contentProvider
deleteContents();
}
}
final userProvider = ChangeNotifierProvider<UserProviderNotifier>(
(ref) {
return UserProviderNotifier();
},
);
If I try to feed UserProvider with ContentProvider like this
final userProvider = ChangeNotifierProvider<UserProviderNotifier>(
(ref) {
final content = ref.watch(contentProvider); // <----
return UserProviderNotifier(content);
},
);
But I know, It won't make sense.
The type of 'userProvider' can't be inferred because it depends on itself through the cycle: contentProvider, userProvider.
Try adding an explicit type to one or more of the variables in the cycle in order to break the cycle.darttop_level_cycle
You can create UserProviderNotifier so it takes ref as an input, like this:
class UserProviderNotifier extends ChangeNotifier {
UserProviderNotifier(this.ref);
final Ref ref;
deleteUsers() {
// here to call a method from contentProvider
ref.watch(contentProvider.notifier).deleteContents();
}
}
final userProvider = ChangeNotifierProvider<UserProviderNotifier>(
(ref) {
return UserProviderNotifier(ref);
},
);
This section of the Riverpod docs mentions this is a common use-case.

Listen to changes on a Future object in Flutter

I need to know wether NFC is active or inactive inside the application. I'm using nfc_manager package and riverpod for state management. Here is my providers and ChangeNotifier class:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:nfc_manager/nfc_manager.dart';
final nfcProvider = ChangeNotifierProvider.autoDispose<NfcService>((ref) {
return NfcService();
});
final nfcIsAvabilableProvider = StreamProvider.autoDispose<bool>((ref) {
ref.maintainState = true;
final stream = ref.watch(nfcProvider).isAvailable.stream;
return stream;
});
class NfcService extends ChangeNotifier {
NfcManager nfcManager = NfcManager.instance;
StreamController<bool> isAvailable = StreamController.broadcast();
NfcService() {
init();
}
init() async {
isAvailable.addStream(nfcManager.isAvailable().asStream());
}
}
When I close the NFC inside the settings, nfcManager.isAvailable() does not return the new state of NFC availability because it is a future not a stream.
So my question is: how to listen to changes on a future object in Flutter?

Flutter: Non-nullable instance field 'currentLocation' must be initialised

I'm getting an error with the new flutter changes on null safety. Am not sure whether I am supposed to add final/late/! in this code.
I have a code to connect to Google Maps and now I want to be able to store the data collected.
Right now I facing an error at the Application Bloc: Non-nullable field 'currentLocation' must be initialized.
import 'package:flutter/material.dart';
import 'package:hawkepedia/services/geolocator_Services.dart';
import 'package:geolocator/geolocator.dart';
class ApplicationBloc with ChangeNotifier {
final geoLocatorService = GeolocatorService();
//Variables
Position currentLocation;
//fire function when the app starts
ApplicationBloc(){
setCurrentLocation();
}
//gets current location
setCurrentLocation() async {
currentLocation = await geoLocatorService.getCurrentLocation();
notifyListeners();
}
}
As it is state in the error, the field causing the issue is currentLocation.
You can:
set it as late meaning you will initilize it during initState for example.
set it a nullable, not sure it is possible in this case, Position? currentLocation. (But it is for primary types)
Initialize it with a neutral value that you know is not initialized yet
Edit:
import 'package:flutter/material.dart';
import 'package:hawkepedia/services/geolocator_Services.dart';
import 'package:geolocator/geolocator.dart';
class ApplicationBloc with ChangeNotifier {
final geoLocatorService = GeolocatorService();
//Variables
late Position currentLocation;
//fire function when the app starts
ApplicationBloc(){
setCurrentLocation();
}
//gets current location
setCurrentLocation() async {
currentLocation = await geoLocatorService.getCurrentLocation();
notifyListeners();
}
}

how to get the old state before updating the state in State Notifier Riverpod?

I am trying to use State Notifier from Riverpod, here is my code
import 'package:flutter_riverpod/flutter_riverpod.dart';
class CitiesState {
List<String> cities = [];
String selectedCity = "New York";
CitiesState({required this.cities, required this.selectedCity});
}
class ChooseCityPageModel extends StateNotifier<AsyncValue<CitiesState>> {
ChooseCityPageModel() : super(AsyncValue.loading());
Future<void> updateCitiesData() async {
state = AsyncValue.loading();
try {
final latestCities = await myAPI.getAvailableCities();
state = AsyncValue.data(
ChooseCityPageState(
selectedCity: '??????', // <------ I need to get the previous selectedCity state
cities: latestCities, // <---- because I just want to update available cities
),
);
} catch (error) {
state = AsyncValue.error(error);
}
}
}
as you can see, I have a method to update available cities from Server. but the state is immutable right? and I need to create the instance again.
but I need to keep the value of selectedCity from previous state, because I just want to update the cities property
how to do that? sorry I am new in Flutter
Read the previous state and use that value to define the new state.
state = AsyncValue.data(
CitiesState(
selectedCity: state.data!.value.selectedCity,
cities: latestCities,
),
);
Utilizing a package like freezed makes this even simpler.
#freezed
class CitiesState with _$CitiesState {
const factory CitiesState({
required List<String> cities,
required String selectedCity,
}) = _CitiesState;
}
state = AsyncValue.data(state.data!.value.copyWith(cities: latestCities));

Can Provider handle state of a complexed class such as this? If not, how can I make this class available to my entire app in its global state?

I would like make a Currency object (shown below) shared throughout my entire app using global state (perhaps this isn't the best way?).
I have tried using the Provider package but I cannot seem to get it working.
class Currency extends ChangeNotifier {
String baseCurrency = 'USD';
Map<String, double> rates = {};
List<String> currencList = [];
Future<Map<dynamic, dynamic>> getLatestCurrencyData() async {
NetworkHelper networkHelper = NetworkHelper('$url_fixer_io$api_key');
var currencyData = await networkHelper.getData();
return currencyData;
}
void populateRates() async {
Map<dynamic, dynamic> currencyData = await getLatestCurrencyData();
Map<String, dynamic> newRates = currencyData['rates'];
newRates.forEach((curr, rate) {
this.currencList.add(curr);
this.rates.putIfAbsent(curr, () => rate.toDouble());
});
notifyListeners();
}
Future<List<String>> getCurrencyList() async {
if (currencList.length == 0) {
await populateRates();
}
return currencList;
}
void changeBaseCurrency(String newCurrency) {
baseCurrency = newCurrency;
notifyListeners();
}
}
The above implementation gives me the following error:
flutter: Another exception was thrown: Tried to use Provider with a subtype of Listenable/Stream (Currency).
I have limited experience managing state on reactive platforms such as flutter, and any tips on how I could accomplish this would be very much appreciated!
It does not matter how complex your class is.
If your class extends ChangeNotifier
Then your class should be made available to the tree by using either
ChangeNotifierProvider
or
ListenableProvider
My guess is you were using Provider instead of this two, which has an assertion set to throw if used with Listenable or Stream(ChangeNotifer is Listenable)
This is a portion of code from Provider class
assert((){
if (value is Listenable || value is Stream) {
throw FlutterError(''')
Instead of Provider use ChangeNotifierProvider or ListenableProvider. The difference is that ChangeNotifierProvider automatically calls the dispose method while you override the dispose method in ListenableProvider