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

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.

Related

Riverpod provider is always null

I am using riverpod for my state manegement in my flutter app.
Riverpod offers a feature for combined providers, but my dependent provider does not update and always returns null.
By clicking one of the pins (secrets) on the map, my "selectedSecretProvider" is updated (default is null). This should trigger the initialization of my audio player. And by clicking play, the sound of the current _selectedSecret should play. So my "selectedTrackProvder" is dependent on my "selectedSecretProvider":
final selectedTrackProvider = StateNotifierProvider<SelectedTrack, Track>((ref) {
Secret? selectedSecret = ref.watch(selectedSecretProvider);
return SelectedTrack(selectedSecret);
});
Here is my selectedTrack class:
class SelectedTrack extends StateNotifier<Track> {
SelectedTrack(this.selectedSecret) : super(Track.initial());
Secret? selectedSecret;
#override
void dispose() {
...
}
void initAudioPlayer() {
...
}
Future<int> play() async {
print(selectedSecret);
return ...
}
}
So why does it always print null, when clicking play?
(Btw. my bottom_panel_sheet is showing the correct data and also consumes the "selectedSecretProvider".)
I wouldn't say the way you're creating your StateNotifierProvider is wrong, but I think the following is a better approach that should solve your problem.
final selectedTrackProvider = StateNotifierProvider<SelectedTrack, Track>((ref) {
return SelectedTrack(ref);
});
class SelectedTrack extends StateNotifier<Track> {
SelectedTrack(this.ref) : super(Track.initial());
final ProviderReference ref;
Future<int> play() async {
final selectedSecret = ref.read(selectedSecretProvider);
print(selectedSecret);
return ...
}
}
This way you don't create a new StateNotifier every time the selectedSecretProvider updates, instead opting to read the current value of the selectedSecretProvider when attempting to call play.

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

How to make a stopwatch that takes a widget off the air?

I need to know how to make this stopwatch, which after 48 hours disables a widget, which in my case is a button. Can someone explain to me how to do it? What classes to use?
I tried to use this, but don't works:
var timer = Timer(Duration(seconds: 1), () => print('done'));
it seems to me that you want this button to be disabled after 2 days of the app that was installed, you need persist the date on the device so that after app-restarts the date will be itself, you need to use a package the persists the data on the device. i recommend shared_preference which is easy to use.
for your case, in the screen where you use the button you need to do this
import 'package:shared_preferences/shared_preferences.dart';
class MyFirstStatefullScreen extends StatefullWidget {
MyFirstStatefullScreenState createState() => MyFirstStatefullScreenState();
}
class MyFirstStatefullScreenState extends State<MyFirstStatefullScreen>{
// some other code that u wrote
bool shouldButtonBeActive = true;
#override
void initState() {
Future.delayed(Duration(0))
.then((_) {
SharedPreferences sharedPrefs= await SharedPreferences.getInstance();
final deadLine = sharedPrefs.getString('deadLine');
if(deadLine == null) {
// this is the first time the app has been installed on the device
// so we need to set the deadLine to N number of days after the installation
final deadLineDate = DateTime.now().add(Duration(days: 2)); // 2 days from the date that the app was installed;
sharedPrefs.setString(deadLineDate.toIso8601String()); // set it so we can check on the successfull state
return;
}
final deadLineDate = DateTime.parse(deadLine); // since we stored it as a string;
/// the deadline is set and is not null
if(DateTime.now().compareTo(deadLineDate) == 0) {
we are successfull, N hours have passed since the intial instalation, now we can disable the button
shouldButtonBeActive = false; // the button should be disabled
}
})
}
Widget build(context) {
// in your UI where you use the button
MaterialButton(
child: Text("Button"),
onPressed: shouldButtonBeActive ? func() : null
)
}
}
PS: we are using the Future inside the initState, because initState dose not allow async ( api calls, storage access) in it.

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

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

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.