alexeieleusis / greencat

A port of Redux(https://github.com/reactjs/redux) to Dart, including Redux Thunk(https://github.com/gaearon/redux-thunk) and a simple Logger.
Apache License 2.0
48 stars 4 forks source link

Closing Store And Opening Store #17

Closed theobouwman closed 7 years ago

theobouwman commented 7 years ago

I have one Store which handles all state and I have made this store a singleton to use it in multiple components in my Flutter app.

When a Flutter Widget disposes the store's close() method should be called so that it wont listen to any changes to avoid memory leaks right?

But when I use that same store in another Widget, after is has been closed by the previous Widget, it will throw and say that the store has already been closed.

This is my workaround, totally not ideal:

store.stream.listen((AuthState state) {
  if (mounted) {
    setState(() {});
  }
});

How can I fix this problem?

brianegan commented 7 years ago

tl;dr -- You want to cancel your StreamSubscription, not close the Store in order to stop receiving updates from the Stream & running code in response. Example:

class MyState extends State<MyWidget> {
  StreamSubscription<int> streamSubscription;

  @override
  initState() {
    // Listen returns a `StreamSubscription`. Capture this value as a class variable so you can `cancel` (aka "unsubscribe") later!
    streamSubscription = store.stream.listen((val) {
      // Your code here
    });

    super.initState();
  }

  @override
  dispose {
    // Now we cancel the subscription! "Your code here" will no longer execute in response to state changes.
    streamSubscription.cancel();

    super.dispose();
  }
}

Longer explanation:

Under the hood, Greencat is using something called a StreamController. These are classes that create their own Streams internally, and allow you to add data or errors to that Stream, which gets sent to your .listen calls. They can also be closed. When closed, the StreamController and it's underlying Stream get shut down and can no longer be used! This is what you're running into. By calling store.close(), Greencat is actually closing it's own internal StreamController & Stream that powers all of the store.stream calls. It will certainly ensure you receive no more events from the Stream! Unfortunately, it will also close down all streams as you've found out.

If you simply want to stop executing code in myFunction that you setup as part of a .listen(myFunction), you can cancel the StreamSubscription. The listen method returns a StreamSubscription. You can capture the StreamSubscription as a class variable and then use it later in the dispose method of a State class to cancel the subscription.

This will keep your Greencat store up and running so other Widgets work fine while ensuring your local code is no longer executed in response to state changes!


Alternative solution: Use the StreamBuilder widget! The takes care of all the subscription business for ya!

class MyStreamyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new StreamBuilder(
      stream: store.stream,
      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
        // Build your widget here with the latest Stream data contained within the `snapshot`
      }
  }
}
theobouwman commented 7 years ago

Thanks. Great explanation!!