progNewbie
progNewbie

Reputation: 4832

Awesome_Notifications - Bad state: Stream has already been listened to

I am using this flutter library to handle notifications. In the initState() in my HomeView I initialize this listener:

_notificationsActionStreamSubscription = AwesomeNotifications().actionStream.listen((receivedNotification) {
  print("user tapped on notification " + receivedNotification.id.toString());
});

Later when someone logs in or signs out of my app I call these lines:

Navigator.of(context).popUntil((route) => route.isFirst);
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => HomeView()));

This let's the initState() of my HomeView be called again which results in the error:

Bad state: Stream has already been listened to.

That is why I did this before calling the two Navigator lines from above, but without success:

AwesomeNotifications().createdSink.close();
_notificationsActionStreamSubscription.cancel();

What am I doing wrong? I cancel the streamsubscription so why do I still get this error message?

Thanks for your advice!

Upvotes: 9

Views: 3175

Answers (3)

Godwin Mathias
Godwin Mathias

Reputation: 524

Move the listeners to the initState method of the class. Suppose you were listening from the build method of the material app widget.

Instead of :

class _MyHomePageState extends State<MyHomePage> {

   @override
  Widget build(BuildContext context) {
   // Listener
   AwesomeNotifications().actionStream().listen((event) {});
  //
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }

Do this:

class _MyHomePageState extends State<MyHomePage> {

 @override 
  void initState() {
    super.initState();
   AwesomeNotifications().actionStream().listen((event) {});
   }

   
   @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }

Upvotes: 0

AshishB
AshishB

Reputation: 817

According to the docs, the listeners should be a top-level function and implemented in the main.dart file. You should basically move all the listeners to a different function or class and then call it in the main function

void main(){
  WidgetsFlutterBinding.ensureInitialized();
  notificationInit();
  runApp(MaterialApp(home: YourApp()));
}

void notificationInit() async {
  await AwesomeNotifications.initialize(...);
  _notificationsActionStreamSubscription = ...;
  _notificationsCreatedStreamSubscription = ...;
  _notificationsDisplayedStreamSubscription = ...;
  _notificationsDismissedStreamSubscription = ...;
}

Upvotes: 3

L. Gangemi
L. Gangemi

Reputation: 3300

The error you get is not caused by _notificationsActionStreamSubscription.

If you read carefully it says

Stream has already been listened to

That means that probably AwesomeNotifications().actionStream can only handle one listener at a time.

_notificationsActionStreamSubscription.cancel() doesn't seem to work, it is possible that AwesomeNotifications().actionStream doesn't know that the stream listener has been closed.

Every time the page get pushed, it is rebuilt, and AwesomeNotifications() throw an error becouse it thinks that you are attacching a second listener.

So I came with this solution: Move the notificationsActionStreamSubscription to a widget which is parent to HomeView. Create the instance right there in the parent, and then attach it to the actionStream. Every time you call Navigator.pushReplacement send it as a parameter.

HomeView: (I made it a statefullwidget)

class HomeView extends StatefulWidget {

  final StreamSubscription<ReceivedAction> notificationsActionStreamSubscription;
  const HomeView({Key key, @required this.notificationsActionStreamSubscription}) : super(key: key);
  
  @override
  _HomeViewState createState() => _HomeViewState();
}

class _HomeViewState extends State<HomeView> {

  //You can access with widget.notificationsActionStreamSubscription
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('HomeView '),
      ),
      body: Container(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [],
            )
          ],
        ),
      ),
    );
  }
  
}

In your parent widget (I suppose it is a statefull widget):

  StreamSubscription<ReceivedAction> notificationsActionStreamSubscription;

  @override
  void initState() {
    super.initState();
    notificationsActionStreamSubscription =
        AwesomeNotifications().actionStream.listen((receivedNotification) {
      print(
          "user tapped on notification " + receivedNotification.id.toString());
    });
  }

And every time you push the HomeView:

Navigator.of(context).popUntil((route) => route.isFirst);
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => HomeView(notificationsActionStreamSubscription:
                    notificationsActionStreamSubscription)));

By doing so, notificationsActionStreamSubscription will not be rebuilt and attached to AwesomeNotifications().actionStream every time you push the HomeView.

Upvotes: 1

Related Questions