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

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

Related

How can i firestore stream snapsnot pause

I have a structure made up of tabs. I'm listening which tab you're in. I am using AutomaticKeepAliveClientMixin in tabs
late StreamSubscription _subscription;
#override
void initState() {
super.initState();
_subscription = FirebaseFirestore.instance
.collection('users')
.doc(id)
.snapshots().listen((DocumentSnapshot snapshot) => _onUpdate(snapshot));
widget.tab?.addListener(_tabListener);
}
#override
void dispose() {
_subscription.cancel();
widget.tab?.removeListener(_tabListener);
super.dispose();
}
_tabListener () {
if(widget.tab?.page == 0.0) {
if(_subscription.isPaused) {
_subscription.resume();
}
} else {
_subscription.pause();
}
}
No problem so far, it stops listening and picks up where it left off, but when it starts again it brings all the changes that happened when it stopped.
Keeps sending snapshot changes constantly in the background.
I want the stream to stop completely when the tab changes, and start over when it comes to tab, how can I do that?
According to the StreamSubscription pause method documentation, if the subscription receives events while paused they will be buffered until the subscription is resumed.
As explained in this paragraph the recommended solution to avoid buffering events would be to cancel the subscription and start it again when needed.

Flutter: how to use .addListener of TextEditingController

i'm building a simple app that prints the result of the current TextFormField. Such as when the text changes it prints the new value.
I found out that you can achieve this with TextEditingController.addListener that listens for changes and executes a function.
So i wrapped it all in initState as follows
#override
void initState() {
addressController.addListener(() {
print(addressController.text);
});
The problem I have is that sometimes it records changes even when there aren't any:
This is what happens writing a word and then deleting it.
If you add listener then you should remove it somewhere, otherwise there can be situation when TextEditingController will have 2 or more listeners:
#override
void initState() {
addressController.addListener(_addressControllerListener);
super.initState()
}
void _addressControllerListener() {
print(addressController.text);
}
#override
void dispose() {
addressController.removeListener(_addressControllerListener);
super.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();
}

didChangeAppLifecycleState doesn't work as expected

I hope I understand how didChangeAppLifecycleState worked correctly.
I have page A and page B . When I click the back device button from page B ( Navigator.of(context).pop(); ), I expect didChangeAppLifecycleState in pageA will get called, but it doesn't.
PageA
class _ABCState extends State<ABCrList> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
....
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
setState(() {
print(...);
});
}else{
print(state.toString());
}
}
....
This is the initState in pageA. The function used to call backend service.
#override
void initState() {
super.initState();
_bloc.getList(context); // return list and populate to ListView
});
}
The way you're thinking it is Android's way where onResume works, but in Flutter, things don't happen this way.
Generally, this gets called when the system puts the app in the background or returns the app to the foreground.
There are mainly 4 states for it:
resumed: The application is visible and responding to user input.
inactive: The application is in an inactive state and is not receiving user input.
paused: The application is not currently visible to the user, not responding user input, and running in the background.
detached: The application is still hosted on a flutter engine but is detached from any host views.
Edit:
When you're navigating to PageB from PageA, use something like:
Navigator.pushNamed(context, "/pageB").then((flag) {
if (flag) {
// you're back from PageB, perform your function here
setState(() {}); // you may need to call this if you want to update UI
}
});
And from PageB, you'll can use
Navigator.pop(context, true);

Flutter Capture event when app comes to Foreground from triggered Intent

I am trying to pull off some basic stuff here. Scenario: I am checking for GPS status on init() using isLocationServiceEnabled. If the GPS is off, I'm showing a popup that redirects to Location settings using AndroidIntent. If hit back without turning on the GPS, I want to capture the event when my app comes to foreground. I guessed it has to do with the lifecycle and tried like below, nothing gets print on the console
AppLifecycleState _notification;
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_notification = state;
print('onResumed called 1');
print(_notification);
});
if( state == AppLifecycleState.resumed){
print('onResumed called 2');
}
}
Am I missing something here?
Did you extend class with WidgetsBindingObserver like so:
class _WhateverWidget extends State<WhateverWidget> with WidgetsBindingObserver
and then initialize an instance like so:
#override void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState();
}
#override void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}