Flutter Getx Stream value changes to Instance after InIt - flutter

I'm using Getx to bind a stream to userDataModel. On initialization, the value is printed from the firestore database, but later the values are null.
When are try to print the value by using print(_userDataController.userDataModel.value.foodData); It prompts null.
PS: In a previous project, I used the identical code. There, it still works.
The code is as follows
UserModel:
Map? foodData;
UserDataModel({this.foodData});
factory UserDataModel.fromMap({dynamic dbData}) {
return UserDataModel(
foodData: dbData['foodData'],
);
}
}
Controller
class UserDataController extends GetxController {
// ================================= > Stream READ
/// Stream User Model
Rx<UserDataModel> userDataModel = UserDataModel().obs;
/// Stream
Stream<UserDataModel> dbStream() {
return FirebaseFirestore.instance
.collection('Users')
.doc('user1')
.snapshots()
.map(
(ds) {
var _mapData = ds.data();
print(_mapData); // ONINIT THIS DATA IS PRINTING BUT LATER IT PROMPTS THE ABOVE ERROR
UserDataModel extractedModel = UserDataModel.fromMap(dbData: _mapData);
return extractedModel;
},
);
}
/// FN to bind stream to user model
void bindStream() {
userDataModel.bindStream(dbStream());
}
// ================================= > OnInIt
#override
void onInit() {
bindStream();
super.onInit();
}
}

To know the content of the Instance of '_MapStream<DocumentSnapshot<Map<String, dynamic>>, UserDataModel>' try with foodData.toString()
You are getting this prompt because you are not converting the response into proper DataModel class.
You have to map the json to DataModel class.
For that you can just paste the response which is printed in the console to https://jsontodart.com/ this will prepare the data model class for you. Then you can access the elements by iterating through them and getting corresponding instance variable
For reference refer:
How to get data from Firebase Realtime Database into list in Flutter?
Flutter list return Instance of
How to print an Instance?

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?

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 can I get the other controller's variable inside one controller in flutter using getx

This is an issue related to the getx in flutter.
I have 2 controllers. ContractsController and NotificationController.
In ContractsController I have put the value into observer variable by calling the Api request.
What I want now is to get that variable's data in another controller - NotificationController.
How to get that value using getx functions?
ContractsController
class ContractsController extends GetxController {
ExpiringContractRepository _expiringContractRepository;
final expiringContracts = <ExpiringContract>[].obs; // This is the value what I want in another controller
ContractsController() {
_expiringContractRepository = new ExpiringContractRepository();
}
#override
Future<void> onInit() async {
await refreshContracts();
super.onInit();
}
Future refreshContracts({bool showMessage}) async {
await getExpiringContracts();
if (showMessage == true) {
Get.showSnackbar(Ui.SuccessSnackBar(message: "List of expiring contracts refreshed successfully".tr));
}
}
Future getExpiringContracts() async {
try {
expiringContracts.value = await _expiringContractRepository.getAll(); // put the value from the api
} catch (e) {
Get.showSnackbar(Ui.ErrorSnackBar(message: e.toString()));
}
}
}
The expiringContracts is updated successfully with data after the api request.
Now, I want to get that value in NotificationController
NotificationController
class NotificationsController extends GetxController {
final notifications = <Notification>[].obs;
ContractsController contractsController;
NotificationsController() {
}
#override
void onInit() async {
contractsController = Get.find<ContractsController>();
print(contractsController.expiringContracts); // This shows an empty list ?????
super.onInit();
}
}
Overview
A couple solutions come to mind:
pass the expiringContracts list as a constructor argument to NotificationsController if you only need this done once at instantiation, or
use a GetX worker to update NotificationsController every time expiringContracts is updated
The first solution isn't related to GetX, rather it's just async coordination between ContractsController and NotificationsController, so lets focus on the 2nd solution: GetX Workers.
Details
In NotificationsController, create a method that will receive expiringContracts.
Something like:
class NotificationsController extends GetxController {
void refreshContracts(List<ExpiringContract> contracts) {
// do something
}
}
Please note: none of this code is tested. I'm writing this purely in StackOverflow, so consider this pseudo-code.
In ContractsController we'll supply the above callback method as a constructor arg:
In ContractsController, something like:
class ContractsController {
final expiringContracts = <ExpiringContract>[].obs
final Function(List<ExpiringContract>) refreshContractsCallback;
ContractsController(this.refreshContractsCallback);
#override
void onInit() {
super.onInit();
refreshContracts(); // do your stuff after super.onInit
ever(expiringContracts, refreshContractsCallback);
// ↑ contracts → refreshContractsCallback(contracts)
// when expiringContracts updates, run callback with them
}
}
Here the GetX ever worker takes the observable as first argument, and a function as 2nd argument. That function must take an argument of type that matches the observed variable, i.e. List<ExpiringContract>, hence the Type of refreshContractsCallback was defined as Function(List<ExpiringContract>).
Now whenever the observable expiringContracts is updated in ContractsController, refreshContractsCallback(contracts) will be called, which supplies the list of expiring contracts to NotificationsController via refreshContracts.
Finally, when instantiating the two controllers inside the build() method of your route/page:
NotificationsController nx = Get.put(NotificationsController());
ContractsController cx = Get.put(ContractsController(nx.refreshContracts));
Timeline of Events
NotificationsController gets created as nx.
nx.onInit() runs, slow call of refreshContracts() starts
ContractsController gets created, with nx.refreshContracts callback
your page paints
nx has no contracts data at this point, so you'll prob. need a FutureBuilder or an Obx/ GetX + StatelessWidget that'll rebuild when data eventually arrives
when refreshContracts() finishes, ever worker runs, sending contracts to nx
nx.refreshContracts(contracts) is run, doing something with contracts
Notes
async/await was removed from nx.onInit
ever worker will run when refreshContract finishes
There were some powerful approaches in GetX. I solved this issue with Get.put and Get.find
Here is the code that I added.
ContractsController
class ContractsController extends GetxController {
ExpiringContractRepository _expiringContractRepository;
final expiringContracts = <ExpiringContract>[].obs; // This is the value what I want in another controller
ContractsController() {
_expiringContractRepository = new ExpiringContractRepository();
}
#override
Future<void> onInit() async {
await refreshContracts();
super.onInit();
}
Future refreshContracts({bool showMessage}) async {
await getExpiringContracts();
if (showMessage == true) {
Get.showSnackbar(Ui.SuccessSnackBar(message: "List of expiring contracts refreshed successfully".tr));
}
}
Future getExpiringContracts() async {
try {
expiringContracts.value = await _expiringContractRepository.getAll(); // put the value from the API
// ******************************** //
Get.put(ContractsController()); // Added here
} catch (e) {
Get.showSnackbar(Ui.ErrorSnackBar(message: e.toString()));
}
}
}
NotificationController
class NotificationsController extends GetxController {
final notifications = <Notification>[].obs;
ContractsController contractsController;
NotificationsController() {
}
#override
void onInit() async {
// ******************************** //
contractsController = Get.find<ContractsController>(); // Added here.
print(contractsController.expiringContracts); // This shows the updated value
super.onInit();
}
}
Finally, I have found that GetX is simple but powerful for state management in flutter.
Thanks.

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.

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.