Better way to handle Future with ChangeNotifier in Flutter - 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");
},
),

Related

Flutter RiverPod: Is it ok to return another provider from build method of Notifier?

I want to keep my return value as AsyncValue rather than Stream so I am returning StreamProvider from build method of Notifier. After reading the codebase of riverpod I can't see any drawback of this, but I have never come across any project doing something like this. Is this fine, or is there any straight forward way to convert Stream to AsyncValue.
final _userProvider = StreamProvider.autoDispose<User?>((ref) {
final repository = ref.watch(repositoryProvider);
return repository.getUser(); //returns Stream<User?>
});
class AuthNotifier extends AutoDisposeNotifier<AsyncValue<User?>> {
#override
AsyncValue<User?> build() {
return ref.watch(_userProvider);
}
Future<void> singOut() {
return ref.read(repositoryProvider).signOut();
}
}
final authProvider =
AutoDisposeNotifierProvider<AuthNotifier, AsyncValue<User?>>(
AuthNotifier.new);
This is fine, yes.
Being able to do such a thing is the goal of the build method & ref.watch
As long as you don't return the provider itself but the value exposed by the provider, there is no problem:
build() {
return ref.watch(provider); // OK
}
build() {
return provider // KO
}

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?

How do I update Flutter's Riverpod values from business logic?

When using Flutter and Riverpod, how do I update its values from my business logic?
I understand that I can get and set values from the UI side.
class XxxNotifier extends StateNotifier<String> {
XxxNotifier() : super("");
}
final xxxProvider = StateNotifierProvider<XxxNotifier, int>((ref) {
return XxxNotifier();
});
class MyApp extends HookConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
// getValue
final String value = ref.watch(xxxProvider);
// setValue
context.read(xxxProvider).state = "val";
return Container();
}
}
This method requires a context or ref.
How do I get or set these states from the business logic side?
Passing a context or ref from the UI side to the business logic side might do that, but I saw no point in separating the UI and business logic. Perhaps another method exists.
Perhaps I am mistaken about something. You can point it out to me.
You can pass ref in your XxxNotifier class:
class XxxNotifier extends StateNotifier<String> {
XxxNotifier(this._ref) : super("");
final Ref _ref;
void setNewState() {
state = 'to setting';
// use `_ref.read` to read state other provider
}
}
final xxxProvider = StateNotifierProvider<XxxNotifier, int>((ref) {
return XxxNotifier(ref);
});
// or using tear-off
final xxxProvider = StateNotifierProvider<XxxNotifier, int>(XxxNotifier.new);
You can create methods in your XxxNotifier class to modify the state of your provider.
For example, your notifier class can look like this.
class TodosNotifier extends StateNotifier <List<Todo>> {
TodosNotifier(): super([]);
void addTodo(Todo todo) {
state = [...state, todo];
}
}
You can then read the provider in a callback.
ref.read(xxxProvider.notifier).addTodo(todo);

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.