Flutter Firebase stops listening after push and pop operation - flutter

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

Related

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

"Bad state: Stream has already been listened to" occurs when I visit screen multiple times

I'm using flutter_bluetooth_serial library and in initState() function I'm using listen to call a function. It's working fine when the app initially starts but when I visit this screen for the second time on the app I get a red screen saying "Bad state: Stream has already been listened to".
I'm new to flutter so please provide the exact code that can help me resolve this issue.
#override
void initState() {
super.initState();
widget.connection.input.listen(_onDataReceived).onDone(() {
// Example: Detect which side closed the connection
// There should be `isDisconnecting` flag to show are we are (locally)
// in middle of disconnecting process, should be set before calling
// `dispose`, `finish` or `close`, which all causes to disconnect.
// If we except the disconnection, `onDone` should be fired as result.
// If we didn't except this (no flag set), it means closing by remote.
if (isDisconnecting) {
print('Disconnecting locally!');
} else {
print('Disconnected remotely!');
}
if (this.mounted) {
setState(() {});
}
});
}
Try to override dispose() method of the state and cancel subscription within it. To do that you need to save subscription in a variable:
StreamSubscription _subscription;
#override
void initState() {
super.initState();
_subscription = widget.connection.input.listen(_onDataReceived, onDone: () {
...
});
}
override
void dispose() {
_subscription.cancel();
super.dispose();
}
Edit
If you need to subscribe to the connection.input multiple times across the app - you can transform it to broacast stream and subscribe for it. It should help. Like this:
final broadcastInput = connection.input.asBroadcastStream();
But if you need to use connection only in this widget I would recommend you to keep it inside state (not widget) and close it on dispose. It would be better lifecycle control solution.
BluetoothConnection _connection;
#override
void initState() {
super.initState();
_initConnection();
}
Future<void> _initConnection() async {
_connection = await BluetoothConnection.toAddress(address);
/// Here you can subscribe for _connection.input
...
}
#override
void dispose() {
connection;
super.dispose();
}

flutter: Update values state in separate class with stream instead of setState()?

Im currently building an app that gets data from a separate class.
When the list devices gets filled, it should add an container to a Scrolable list.
I already managed to do that part, but.. cause the state of the devices list won't update on change the ui won't add an container either. And I can't use set state in the separate class..
void initState() {
// TODO: implement initState
super.initState();
getDevices();
}
Future<void> getDevices() async {
setState(() async {
deviceLength = drs.devices.length;
await drs.allDevices(specificDevices: 'DRS');
});
}
set state only updates deviceLength on start, but won't continue.
The length of devices is defined by following stream in a separate class Bluetooth, that adds objects to the List devices :
Future<Stream<void>> _findSingleResult({searchFor: ''}) async {
_flutterBlue.scanResults.listen((results) async {
// Return found Devices
try {
print('Device Found ? $deviceFound');
print(results.last.device.name);
//Does found device equal searched Device?
if (results.last.device.name.contains(searchFor)) {
deviceFound = true;
devices.add(results.last.device);
print('Device found...');
await _flutterBlue.stopScan();
return;
}
_counter++;
} catch (e) {
print(e);
}
});
}
If anyone knows how to solve this Issue pls help :)

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.