how to make more than one asynchronous calls when open a page using Riverpod? - flutter

when the user of my app open the Home page, I need to make two asynchronous calls to the server
first, I need to get current user data.
and then based on the current user data, I need fetch his favorite restaurants.
I have two separate methods to get those data from server
class MyAPI {
Future<User> getUserData() async {}
Future<List<Restaurant>> getUserData() async {}
}
then how do I construct those 2 asynchronous methods in my HomePage using Riverpod?
show circular loading indicator
make those 2 asynchronous calls
hide circular loading indicator and load lisView
I know about FutureProvider from Riverpod, but FutureProvider is only for one asynchronous service right?
do I need to somehow combine those two into a single method first and then use FutureBuilder? or is it another way that more common to use? I am not sure
how to solve this issue. sorry I am a beginner in Flutter

This look like a use case of a stateNotifier:
first in your data model define a UserData class :
class UserData{
final User user;
final List<Restaurant> restaurants;
UserData(this.user, this.restaurants)
}
next define a state and it's associated stateNotifierProvider :
final userDataProvider = StateNotifierProvider<UserDataNotifier, AsyncValue<UserData>>((ref) => UserDataNotifier());
class UserDataNotifier extends StateNotifier<AsyncValue<UserData>> {
UserDataNotifier() : super(AsyncValue.loading()){
init();
}
final _api = MyAPI();
void init() async {
state = AsyncValue.loading();
try {
final user = await _api.getUser;
final List<Restaurant> restaurants = await _api.getFavoriteRestaurant(user);
state = AsyncValue.data(UserData(user,restaurants));
} catch (e) {
state = AsyncValue.error(e);
}
}
}
finally in you UI use a consumer :
Consumer(builder: (context, watch, child) {
return watch(userDataProvider).when(
loading: ()=> CircularProgressIndicator(),
error: (error,_) =>
Center(child: Text(error.toString())),
data: (data) => Center(child: Text(data.toString())));
})

Related

Flutter redux store.dispatch(...) resetting the value of another redux state variable

The scenario is, when the app opens, we need to do two REST API calls,
Get User Function List API call
Get Chat Bubble List API call
We have two redux state variable
userFunctionList
chatBubbleList
state.dart
class AppState {
final List userFunctionList;
final List chatBubbleList;
const AppState({
required this.userFunctionList,
required this.chatBubbleList,
});
AppState.initialState()
: userFunctionList = [],
chatBubbleList = [];
}
model.dart
class AddUserFunctionList {
late final List userFunctionList;
AddUserFunctionList({
required this.userFunctionList,
});
}
class AddChatBubbleList {
late final List chatBubbleList;
AddChatBubbleList({
required this.chatBubbleList,
});
}
store.dart
final store = new Store(
appReducer,
initialState: new AppState.initialState(),
);
reducer.dart
List userFunctionsListReducer(List existingData, dynamic action) {
if (action is AddUserFunctionList) {
return action.userFunctionList;
}
return [];
}
List chatBubbleListReducer(List existingData, dynamic action) {
if (action is AddChatBubbleList) {
return action.chatBubbleList;
}
return [];
}
AppState appReducer(AppState state, dynamic action) {
return new AppState(
chatBubbleList: chatBubbleListReducer(state.chatBubbleList, action),
userFunctionList: userFunctionsListReducer(state.userFunctionList, action),
);
}
On the homepage of the app, initState() function, we are doing two API calls,
getUserFunctionList()
getChatBubbleList()
In every function after receiving response, we have store.dispatch() method, like below,
At the end of function 1,
store.dispatch(AddUserFunctionList(userFunctionList: response['data']));
At the end of function 2,
store.dispatch(AddChatBubbleList(chatBubbleList: response['data]));
And the StoreConnector inside the widget builder like,
....
....
StoreConnector<AppState, List>(
converter: (store) => store.state.userFunctionList,
builder: (context, userFunctionList) {
return UserFunctionListView(
userFunctionList: userFunctionList,
);
}
....
....
If I comment out the second function and call only the first API (getUserFunctionList()), the data updates happening on the redux variable, I am able to see the UI.
But If the second function also doing the store.dispatch... action, the first redux variable gets replaced with the initial value ([]).
Not able to do two store.dispatch action continuously.
Moreover, currently not using any middleware.
How to do two different store.dispatch calls while opening the app?

GetX Controller - Wait for firebase RTDB stream to be populated

I am new to GetX and want to get some concepts right. This is my process to get the groups that a current user is in using Firebase Realtime Database:
Create an AuthController to get current user id (works perfectly)
class AuthController extends GetxController {
FirebaseAuth auth = FirebaseAuth.instance;
Rx<User?> firebaseUser = Rx<User?>(FirebaseAuth.instance.currentUser);
User? get user => firebaseUser.value;
#override
void onInit() {
firebaseUser.bindStream(auth.authStateChanges());
super.onInit();
}
} ```
Create a UserController to get ids of groups that the user has (working partially)
class FrediUserController extends GetxController {
Rx<List<String>> groupIdList = Rx<List<String>>([]);
List<String> get groupIds => groupIdList.value;
#override
void onInit() {
User? user = Get.find<AuthController>().user;
if (user != null) {
String uid = user.uid;
groupIdList.bindStream(DatabaseManager().userGroupIdsStream(uid));
print(groupIds); //prints [] when it should be populated
}
super.onInit();
}
}
Create a GroupsController to get the groups from those ids (not working) --> Dependant on UserController to have been populated with the id's.
class FrediGroupController extends GetxController {
Rx<List<FrediUserGroup>> groupList = Rx<List<FrediUserGroup>>([]);
List<FrediUserGroup> get groups => groupList.value;
void bindStream() {}
#override
void onInit() {
final c = Get.find<FrediUserController>();
List<String> groupIds = c.groupIds;
print(groupIds); //prints [], when it should have ids
groupList.bindStream(DatabaseManager().groupsStream(groupIds)); //won't load anything without id's
super.onInit();
}
}
Observations
Get.put is called sequentially in the main.dart file:
Get.put(AuthController());
Get.put(FrediUserController());
Get.put(FrediGroupController());
Inside my HomePage() Stateful Widget, if I call the UserController, the data loads correctly:
GetX<FrediUserController>(
builder: (controller) {
List<String> groups = controller.groupIds;
print(groups); //PRINTS the list of correct ids. THE DATA LOADS.
return Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: groups.length,
itemBuilder: (context, index) {
return Text('${groups[index]}',
style: TextStyle(color: Colors.white));
},),); },),
QUESTION
It is as if the stream takes some time to populate, but the UserController doesn't wait for it and initializes the controller as empty at first, but after some time it populates (not in time to pass the data to the GroupController.
How can I fix this? I have tried async but not with much luck.
Behaviour I would Like:
Streams may/may not be ready, so it can initialize as empty or not.
HOWEVER, if the stream arrives, everything should be updted, including the initialization of controllers that depend on UserController like GroupController.
Consequently, the UI is rebuilt with new values.
THANK YOU!
There are two things that you can add:
Future Builder to show some loading screen while it fetch data from RTDB
ever function
class AuthController extends GetxController {
late Rx<User?> firebaseuser;
#override
void onReady() {
super.onReady();
firebaseuser = Rx<User?>(FirebaseAuth.instance.currentUser);
firebaseuser.bindStream(FirebaseAuth.instance.idTokenChanges());
ever(firebaseuser, _setInitialScreen);
}
_setInitialScreen(User? user) {
if (user != null) {
//User Logged IN
} else {
//User Logged out
}
}
}
You only take the user once, in the method onInit. You are not getting user changes. To get every change you would have to use "ever" function. For example, "firebaseUser.value" is like a photography of the firebaseUser observable in the moment.
If I can make a sugestion, don't mistake controllers with providers. Think Firebase as a provider and the controller as a mid point between the UI and the provider. You can listen to Firebase Streams at the controller to update UI and make calls from the UI change parameters in your Firebase provider. Separate your concerns into two distinct classes and you'll, potentially, have a better design.
Use of "ever" function example:
ever(firebaseUser, (user) {
// do something
});
"Ever" assigned function runs whenever the observable emits a new value

Flutter/Dart : How to wait for asynchronous task before app starts?

I am working on a dart application where I want to fetch the data present in cache (SharedPreferences) and then show it on UI (home screen) of the app.
Problem : Since SharedPreferences is an await call, my home page loads, tries to read the data and app crashes because data fetch has not yet happened from SharedPreferences, and app loads before that.
How can I not start the app until cache read from SharedPreferences is done?
This is required because I have to display data from SharedPreferences on home page of the app.
Various view files of my project call static function : MyService.getValue(key) which crashes as cacheResponseJson has not populated yet. I want to wait for SharedPreferences to complete before my app starts.
Class MyService {
String _cacheString;
static Map < String, dynamic > cacheResponseJson;
MyService() {
asyncInit();
}
Future < void > asyncInit() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
_cacheString = sharedPreferences.getString(“ConfigCache”);
cacheResponseJson = jsonDecode(ecsCacheString);
}
static String getValue(String key) {
return cacheResponseJson[key];
}
}
void main() {
MyService s = MyService();
}
Any help would be highly appreciated!
You can run code in your main() method, before the call to runApp() that kicks off your application.
For example:
void main() async {
WidgetsFlutterBinding.ensureInitialized(); // makes sure plugins are initialized
final sharedPreferences = MySharedPreferencesService(); // however you create your service
final config = await sharedPreferences.get('config');
runApp(MyApp(config: config));
}
Can you try wrapping the function asyncInit() in initstate then in the function then setstate the values
_cacheString = sharedPreferences.getString(“ConfigCache”);
cacheResponseJson = jsonDecode(ecsCacheString);
I hope it works.
avoid using initialization etc outside the runApp() function, you can create a singleton
class MyService{
MyService._oneTime();
static final _instance = MyService._oneTime();
factory MyService(){
return _instance;
}
Future <bool> asyncInit() async {
//do stuff
return true;
}
}
and incorporate that in the UI like this
runApp(
FutureBuilder(
future: MyService().asyncInit(),
builder: (_,snap){
if(snap.hasData){
//here you can use the MyService singleton and its members
return MaterialApp();
}
return CircularProgressIndicator();
},
)
);
if you take this approach you can do any UI related feedback for the user while the data loads

How to wait on getter in flutter?

Below is the code of a provider class. Whenever the app starts i want to get the forms which were saved in the shared preferences. However it is taking sometime to load the from sharedpreferences. So When i access the forms for the first time it is initially empty, im getting an empty list. Is there anyway to delay the getter until it has the objects of form model.
class FormProvider with ChangeNotifier {
FormProvider() {
loadformPreferences();
}
List<FormModel> _forms = [];
List<FormModel> get forms => _forms;
Future<void> saveForm(FormModel form) async {
_forms.add(form);
await saveformPreferences();
notifyListeners();
}
Future<void> saveformPreferences() async {
List<String> myforms = _forms.map((f) => json.encode(f.toJson())).toList();
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setStringList('forms', myforms);
}
Future<void> loadformPreferences() async {
// WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
var result = prefs.getStringList('forms');
if (result != null) {
_forms = result.map((f) => FormModel.fromJson(json.decode(f))).toList();
}
}
}
In Flutter, all actions related to building the UI must not be asynchronous (or expensive) in order to main a high frame-rate.
Thus, even if you could hypothetically find a way to "wait" for the results from SharedPreferences, this would not be desirable, since whatever waiting was done would block the UI from making progress.
Thus, there are a couple approaches to this common problem:
Handle initial state explicitly in the UI
The simplest solution is to explicitly handle the case where the Provider has not yet fetched its data, by representing some sort of initial state in your Provider. One easy way to do this is to initialize _forms to null instead of []. Then in your Widget.build method, you could do something specific (like show a loading spinner) when the result is null:
Widget build(BuildContext context) {
final provider = Provider.of<FormProvider>(context);
if (provider.forms == null) {
return CircularProgressIndicator();
}
// Otherwise, Do something useful with provider.forms
}
Construct your provider with the resolved data
Let's say that you don't use the FormProvider until the user performs an action, like click a button, at which point you push a new view onto the navigator with the FormProvider.
If you wish to guarantee that the FormProvider will always be initialized with the SharedPreferences values, then you can delay the construction of the new view until SharedPreferences has finished:
class MyButton extends StatelessWidget {
Widget build(BuildContext context) {
return Button(onClick: () async {
final forms = await _fetchFormsFromSharedPrefs();
Navigator.push(context, MaterialPageView(builder: (context) =>
Provider(create: (_) => FormProvider(forms))));
});
}
}

Flutter with provider pattern: How and where to get async data

When using the provider pattern in Flutter, I do not understand how and where to fetch (async) data from the database or an API.
The tutorials seem toconveniently omit this use case which is quite central.
So with something like
class ToDo with ChangeNotifier {
get todos async {
if(_todos == null) {
_todos = await MyApi.fetchToDos();
}
return _todos;
}
}
Where and how would I actually fetch this data?
Should I always use a FutureBuilder? Or should it be fetched in some wrapper widget at the top and passed down?
You could fetch the data in the constructor.
class ToDo extends ChangeNotifier {
ToDo() {
_fetchToDos()
}
}
You could also notify listener when the fetch is done.
More information on the provider package doc.