Can't archive single data in init state from cloud firestore - flutter

I want archive a number from cloud firestore in init state ..below is my code how am trying to archive but it not working ..can anyone tell me how to do that ........
int _prvScore ;
// #protected
// #mustCallSuper
// void initState() {
// _prvScore = 23.toInt();
// }
#override
void initState() {
var month = new DateTime.now().month;
final DocumentReference documentReference =
Firestore.instance.collection('quiz').document('$month')
.collection('1')
.document(username);
subscription =
documentReference.snapshots().listen((datasnapshot) {
if (datasnapshot.data.containsKey("total score")) {
_prvScore = datasnapshot.data['total score'].toInt();
}
});
}

You need to call setState(...) to cause a rebuild.
If you execute async code, the result will not yet be available when the function is completed and build() already executed.
Async code is only executed eventually later.
Changing
_prvScore = datasnapshot.data['total score'].toInt();
to
setState(() => _prvScore = datasnapshot.data['total score'].toInt());
should fix it.
You need to ensure build() doesn't cause an error when _prvScore is still null because no data arrived yet from Firebase.

Related

How to `setState()` in a global function?

Here's a simple code block for detecting transactions from SMS :
FlutterLocalNotificationsPlugin notificationsPlugin =
FlutterLocalNotificationsPlugin();
onBackgroundMessage(SmsMessage message) async {
log("Message recieved in background");
_TransactionState().onMessage(message);
}
class Transaction extends StatefulWidget {
const Transaction({Key? key}) : super(key: key);
#override
State<Transaction> createState() => _TransactionState();
}
class _TransactionState extends State<Transaction> {
String _message = "";
var debit = 0.0;
var credit = 0.0;
var last = 0.0;
var transact = 0.0;
final Telephony telephony = Telephony.instance;
#override
void initState() {
super.initState();
initPlatformState();
}
onMessage(SmsMessage message) async {
setState(() {
bool mesFlag = false;
bool debFlag = false;
if (message.body!.contains('debit')) {
debFlag = true;
} //check whether debit or credit
for (var bank in banks.entries) {
if (message.address!.contains(bank.key)) {
_message = message.body?.split(
' ')[(debFlag ? bank.value.item2 : bank.value.item1)] ??
"Error reading transaction!";
_message = _message.replaceAll(',', ''); //remove comma
if (_message.contains("Rs.")) {
_message = _message.substring(3);
} else if (_message.contains("Rs")) {
_message = _message.substring(2);
} // remove Rs. and Rs
showNotification("Last transaction amount: $_message");
transact = double.parse(_message);
mesFlag = true;
if (debFlag) {
debit += transact;
last = -transact;
} else {
credit += transact;
last = transact;
}
limit += last;
if (limit < 0) {
limit = 0;
showNotification("You are over-spending!");
}
transList.add(TransactionTile(trans: last));
break;
}
}
if (!mesFlag) _message = ''; //if not a transaction
});
}
Here's the exception it throws when I receive a SMS in the background :
setState() called in constructor: _TransactionState#f0921(lifecycle state: created, no widget, not mounted)
This happens when you call setState() on a State object for a widget that hasn't been inserted into the widget tree yet. It is not necessary to call setState() in the constructor, since the state is already assumed to be dirty when it is initially created.
The telephony package requires onBackgroundMessage to be a global function. Is there any work around by means of which I can call the onMessage in the global function or setState() cause I need them to be updated even when the app isn't open & it should function exactly like the onMessage fucntion.
Any help appreciated.
From the Firebase Cloud Messaging Flutter documentation on background messages:
Since the handler runs in its own isolate outside your applications context, it is not possible to update application state or execute any UI impacting logic. You can, however, perform logic such as HTTP requests, perform IO operations (e.g. updating local storage), communicate with other plugins etc.
You can execute a logic that affects the UI only when the user decides to click on a notification that contains a message, and the app comes from background to foreground.
If you want't to silently receive some data and update the UI when the app is activated again, you can for example:
save the data in shared preferences which you can access from onBackgroundMessage,
implement a didChangeAppLifecycleState override that checks the data in shared preferences when the app is activated and updated the UI accordingly.

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.

Flutter: Async function in Getx Controller takes no effect when initialized

Updates:
2021/06/11 After hours of debugging yesterday, I confirmed that the problem is caused by aws amplify configuration: _configureAmplify(). Because the location of the amplify server was set wrong, so _configureAmplify() takes several seconds to work... and therefore, the readPost() function did not work on initialization, as it must run after _configureAmplify()...
2021/06/10I made changes to my code according to S. M. JAHANGIR's advice, and updated the question. The issue still presists. The value of posts is not updated when called in initialization and the data only shows up after reload. (if I commented out the _controller.readPost() in UI, the value of posts is always empty.
I have this page that loads information from aws amplify with getx implemented. However, I found out the readPost() async funtion in getx controller dart file is not reading from database, when the controller instance is initialized. I have to add a _controller.readPost() in UI file to make it work. And the data only shows up after a reload of that UI page...
Getx Controller dart file:
class ReadPostController extends GetxController {
var isLoading = true.obs;
var posts = <Posty>[].obs;
#override
void onInit() {
_configureAmplify();
await readPost();
super.onInit();
// print('show post return value: $posts');
}
void _configureAmplify() {
final provider = ModelProvider();
final dataStorePlugin = AmplifyDataStore(modelProvider: provider);
AmplifyStorageS3 storage = new AmplifyStorageS3();
AmplifyAuthCognito auth = new AmplifyAuthCognito();
AmplifyAPI apiRest = AmplifyAPI();
// Amplify.addPlugin(dataStorePlugin);
Amplify..addPlugins([dataStorePlugin, storage, auth, apiRest]);
Amplify.configure(amplifyconfig);
print('Amplify configured');
}
// read all posts from databases
Future readPost() async {
try {
isLoading(true);
var result = await Amplify.DataStore.query(Posty.classType);
print('finish loading request');
result = result.sublist(1);
posts.assignAll(result);
// print(the value of posts is $posts');
} finally {
isLoading(false);
}
}
#override
void onClose() {
// called just before the Controller is deleted from memory
super.onClose();
}
}
And in the UI part:
class TabBody extends StatelessWidget {
TabBody({Key? key}) : super(key: key);
final ReadPostController _controller = Get.put(ReadPostController());
#override
Widget build(BuildContext context) {
_controller.readPost();//if commented out, _controller.post is empty
return Container(
child: Obx(
() => Text('showing:${_controller.posts[1].title}'),
));
}
}
In my understanding, the readPost() function should be called when the ReadPost_controller is initiallized. And the UI will update when the posts = <Posty>[].obs changes. Guys, what am I doing wrong here?
First, when you are calling readPost on onInit you are not awaiting. So change it to:
onInit() async{
...
await readPost();
...
}
Secondly, posts is a RxList so you need to use the assignAll method to update it.
Therefore, in your readPost method, instead of posts.value = reault you need to use posts.assignAll(result)
Calling from the UI works because readPost every time the build method is called by the Flutter framework and actually the UI shows the data from every previous call.
I think try with GetBuilder instead of Obx.
GetBuilder<ReadPostController>(
builder: (value) => Text('showing:${value.posts[1].title}'),
)
and also use update(). in readPost() method.

Proper way to persist / store data in GetXController in flutter

I have this class and use "reactive" state management e.g.
"".obs
Now I plan to initialize my state from local storage (get_storage) onInit()
problem:
where do I persist my data? As soon as some state changes, I want to persist it as well.
I tried using a listener but it never fires.
Currently I have this:
class CosController extends GetxController {
final box = GetStorage();
RxString econtactnr = ''.obs;
#override
void onInit() {
super.onInit();
addListener(() { //NEVER fires
print('hellowwww listener');
});
econtactnr.value = box.read('econtactnr') ?? '';
}
What is a best practice to store state to disk in GetXControllers using reactive state management?
EDIT: I noticed that you can do:
econtactnr.listen((x) {
box.write('econtactnr', econtactnr.value);
});
question: is that ok? do I have to cancel that subscription as well?
GetX provides Workers for this type of functionality. The ever method can listen and store the updated value whenever it changes.
Try this in your onInit
#override
void onInit() {
super.onInit();
econtactnr.value = box.read('econtactnr') ?? '';
ever(
econtactnr,
(value) {
box.write('econtactnr', value);
},
);
}
This will work and persist as long as you have await GetStorage.init(); happening before you initialize with Get.put(CosController());
If you need that to be stored throughout the entire lifecycle of your app, then you don't need worry about disposing it because you always want it listening.
If you do want to dispose it for whatever reason, you could save the listener into a Worker variable.
Worker worker;
#override
void onInit() {
super.onInit();
econtactnr.value = box.read('econtactnr') ?? '';
worker = ever(
econtactnr,
(value) {
box.write('econtactnr', value);
debugPrint(value);
},
);
}
Then dispose by using worker.dispose();

Flutter Firebase stops listening after push and pop operation

I'm saving Firebase data in a list using on child Added listener and on child changed listener, it works good, but when i perform push operation and use pop to back to the screen the listeners stops listening.
Is there any solutions to fix this.
I used this code from Stackoverflow : Code
Sorry for this silly post, i'm new to flutter, I fixed this by using Future method in listeners.
#GrahamD you can find my code here.
My code:
var childAddedListener;
var childChangedListener;
#override
void initState(){
FirebaseDatabase database;
database = FirebaseDatabase.instance;
database.setPersistenceEnabled(true);
database.setPersistenceCacheSizeBytes(10000000);
reference = database.reference().child('Sweets');
childAddedListener = reference.onChildAdded.listen(_onEntryAddedShop);
childChangedListener = reference.onChildChanged.listen(_onEntryChangedShop);
super.initState();
}
On Child Added Listener
Future _onEntryAddedShop(Event event) {
setState(() {
itemsShop.add(Sweets.fromSnapshot(event.snapshot));
});
}
On Child Changed Listener
Future _onEntryChangedShop(Event event) {
print('Child Changed');
var old = itemsShop.singleWhere((entry) {
return entry.id == event.snapshot.key;
});
setState(() {
itemsShop[itemsShop.indexOf(old)] = Sweets.fromSnapshot(event.snapshot);
});
getItemIds();
}
Cancel them in dispose
#override
dispose() {
childAddedListener.cancel();
childChangedListener.cancel();
super.dispose();
}