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

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?

Related

Riverpod FutureProvider - passing result between screens

I'm learning Riverpod provider and stuck on a topic regarding passing values between screens.
As I learnt from Riverpod docs - it delivers a provider that enables values to be accessed globally... and here is my case.
I'm creating a service repo, that contains some methods delivering futures (e.g. network request to verify user):
class VerifyUser {
Future<User> verifyUser(String input) async {
await Future.delayed(const Duration(seconds: 2));
print(input);
if (input == 'Foo') {
print('Foo is fine - VERIFIED');
return User(userVerified: true);
} else {
print('$input is wrong - NOT VERIFIED');
return User(userVerified: false);
}
}
}
Next step is to create providers - I'm using Riverpod autogenerate providers for this, so here are my providers:
part 'providers.g.dart';
#riverpod
VerifyUser verifyUserRepo(VerifyUserRepoRef ref) {
return VerifyUser();
}
#riverpod
Future<User> user(
UserRef ref,
String input
) {
return ref
.watch(verifyUserRepoProvider)
.verifyUser(input);
}
and there is a simple User model for this:
class User {
bool userVerified;
User({required this.userVerified});
}
I'm creating a wrapper, that should take the user to Homescreen, when a user is verified, or take the user to authenticate screen when a user is not verified.
class Wrapper extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
String user = '';
final userFromProvider = ref.watch(userProvider(user));
if (user == 'verified') {
print(userFromProvider);
return MyHomePage();
} else {
return Authenticate();
}
}
}
App opens on Authenticate screen because there is no info about the user.
On Authenticate screen I'm getting input and passing it to FutureProvider for verification.
final vUser = ref.watch(userProvider(userInput.value.text));
When I'm pushing to Wrapper and calling provider - I'm not getting the value I initially got from future.
ElevatedButton(
onPressed: () async {
vUser;
if (vUser.value?.userVerified == true) {
print('going2wrapper');
Navigator.of(context).push(MaterialPageRoute(builder: (context) => Wrapper()));
}},
child: const Text('Verify User'))
Inside Wrapper it seems that this is only thing that I can do:
String user = '';
final userFromProvider = ref.watch(userProvider(user));
But it makes me call the provider with a new value... and causing unsuccessful verification and I cannot proceed to homescreen.
As a workaround, I see that I can pass the named argument to Wrapper, but I want to use the provider for it... is it possible?
I hope that there is a solution to this.
Thx!
David

Better way to handle Future with ChangeNotifier in Flutter

I have an object model in my app which needs to be accessed on different pages. I get the object model via a REST interface and store it in a variable (here simplified SomeObjectModel). Since the object model can change through various events, I decided to use a ChangeNotifier to update the UI.
My problem: The return value from the REST call is an object of type Future<SomeObjectModel> which I assign to my private variable in the asynchronous method _getCurrentState. If I want to read a value from the object model (e.g. actualTemperature) I always have to check if the object model is not null. Is there a better way to implement this?
Here my simplified Code:
ChangeNotifier Class
class CarouselItemModel extends ChangeNotifier {
SomeObjectModel? _someObjectModel;
CarouselItemModel() {
_getCurrentState();
}
_getCurrentState() async {
_someObjectModel = await Rest().getCurrentState();
notifyListeners();
}
double getActualTemperature() {
if (_someObjectModel != null) {
return _someObjectModel!.actualTemperature;
} else {
return 0.0; // Default value if no connection to the server is possible.
}
}
}
Consumer in Widget
Consumer<CarouselItemModel>(
builder: (context, carouselItemModel, child) {
return Text(
"Temperature: ${carouselItemModel.getActualTemperature()} °C");
},
),

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

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())));
})

Why can't I get last document from cloud firestore in flutter using provider package?

I am banging my head with this and I need your help guys. Please help me with this.
I am currently getting streams from firestore and it's working fine, but the problem is I want to implement pagination now and currently, I can't get the value of the last document which is why I can't use startAfter feature. Have a look into my code
Code on parent page i.e. homepage.dart
StreamProvider<List<Cars>>.value(
value: DatabaseService().getCars(),
catchError: (ctx, err) => null,
child: ChangeNotifierProvider(
create: (context) => LastDocumentTracker(),
child: Scaffold()
Code on database Service page:
getCars({bool getMore = false}) {
var collection = carsCollection.orderBy('dueDate').limit(15);
if(!getMore ) {
return collection.snapshots().map((event) {
LastDocumentTracker().changeLastDocument(event.docs.last);
return _carsListFromSnapshot(event);
});
}
}
Now I got a class with ChangeNotifier
class LastDocumentTracker with ChangeNotifier{
List <QueryDocumentSnapshot> _snapshot = [];
QueryDocumentSnapshot get getLastDocument {
return _snapshot.last;
}
void changeLastDocument (QueryDocumentSnapshot doc){
print('Snapshot $_snapshot'); // here I can see the snapshot on console but on other pages where I am listinig its null.
_snapshot.add(doc);
notifyListeners();
}
}
I was thinking to get the value of the last document from the getter getLastDocument however I am unable to get it because it's always null.
Please help me to implement pagination because I don't want a whole bunch of data to be accessed by users at once.
Every time you do LastDocumentTracker(), you are creating a new instance of LastDocumentTracker with _snapshot = []. Hence, you are getting the last element as null. Convert LastDocumentTracker into a singleton:
class LastDocumentTracker with ChangeNotifier{
static LastDocumentTracker _instance;
List <QueryDocumentSnapshot> _snapshot;
LastDocumentTracker._construct() {
_snapshot = [];
}
factory LastDocumentTracker() {
if(_instance == null) _instance = LastDocumentTracker._construct();
return _instance;
}
QueryDocumentSnapshot get getLastDocument {
return _snapshot.last;
}
void changeLastDocument (QueryDocumentSnapshot doc) {
_snapshot.add(doc);
notifyListeners();
}
}
Edit
As you mentioned about the providers, it is better not to go with the singleton answer I provided. Instead, you can replace this:
LastDocumentTracker().changeLastDocument(event.docs.last);
with
final tracker = Provider.of<LastDocumentTracker>(context, listen: false);
tracker.changeLastDocument(event.docs.last);
This way, you are accessing the tracker instance that your provider holds. This is better than the singleton pattern I mentioned as it makes the class reusable using the provider.
Note:
You need context to access provider of that context so pass the context to the getCars method from wherever you are calling it.
set listen to false otherwise, you won't be able to access getCars from methods like buttonPress callbacks or initState etc.

How to retain data while using ScopedModel in Flutter?

I am using ScopedModel to fetch some data from a Firebase database.
I am fetching a list of events.
I fetch the events from the endpoint inside the Model;
I store the events into a List<Event> inside the model;
I use that list to build my ListView.
mixin EventModel on Model {
List<Event> _events = [];
Future<http.Response> fetchEvents() async {
http.Response response = await http.get(//Url);
final List<Event> fetchedEvents = [];
... // decode response data into fetchedEvents
// Add the loaded data to my List
_events = fetchedEvents;
notifyListeners();
...
}
}
So, when opening the EventsPage the first thing I do is to fetch the data in initState().
class _EventPageState extends State<EventPage> {
#override
void initState() {
super.initState();
widget.model.fetchEvents();
}
}
}
After fetching the network data, my List inside my app has the network data so I can use it to build my ListView.
EventsPage.dart
Widget _buildListView(MainModel model) {
return Center(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ItemEventBig(model.events[index], index);
},
itemCount: model.events.length,
),
);
}
My problem is that, if I navigate to another page and then come back to EventsPage, initState() will be called again, so fetchEvents. Making the app reload all the events list again.
I would like to retain the downloaded data while my app is alive, so If the user go and come back to EventsPage the data will not be lost.
I was used to do it in Android using ViewModel, how to do it in Flutter?
I want to keep using ScopedModel to do my State Management in Flutter.
Possible Solution
I thought that a solution would be to store the events in a List<Event> as I am doing. Then, when calling fetchEvents() I could first check if my List<Event> is not null if so, I don't need to call it again because data was already loaded.
This seems a bad solution for me, especially when I have multiple pages fetching the data. Suppose I load the first one, when I go to the second one it will assume the data was already loaded because List<Event> is non null and it will not load again.
See Flutter Documentation - https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html
class _EventPageState extends State<EventPage>
with AutomaticKeepAliveClientMixin<EventPage> {
#override
void initState() {
super.initState();
widget.model.fetchEvents();
}
}
#override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
}