Home > OS >  Geolocator, setState, memory leak
Geolocator, setState, memory leak

Time:11-30

I'm working on geolocation with geolocation 7.6.2 package. I have a problem with memory leak when closing the stream. Widget looks like this:

class _GeolocatorActiveState extends State<GeolocatorActive> {

  StreamSubscription<Position>? _currentPosition;

  double distanceToday = 0.0;
  double distanceTotal = 0.0;

  bool gpsActive = true;

  @override
  void initState() {
    getCurrentPosition();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
       // Some stuff
    ),
    ),
    backgroundColor: Colors.deepOrange
    ),
    body: Column(
      children: [
        // some stuff here
        TextButton(onPressed: () {
          setState(() {
            gpsActive = false;
            stopStream();
          });
          Navigator.pushNamed(context, '/');},
          child: Text(
            'Finish',
          )
        ),
        // Some stuff there
      ],
    ),
    );
  }

  void getCurrentPosition () {
    final positionStream = GeolocatorPlatform.instance.getPositionStream();
    if(gpsActive == true){
      _currentPosition = positionStream.listen((position) {
        setState(() {
          long = position.longitude.toString();
          lat = position.latitude.toString();
        });
      });
    }
  }

  void stopStream () {
    _currentPosition = null;
    _currentPosition?.cancel();
    super.dispose();
  }
}

Now, the thing is that when I push the button "Finish" I want to close and remove this Widget. I tried with Navigator.pushNamedAndRemoveUntil and thought it causes the memory leak but no. I had stopStream function inside getCurrentPosition as else statement but it didn't work either. How can I force app to close stream before it closes this widget? Or am I missing something?

Error looks like this:

E/flutter ( 4655): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: setState() called after dispose(): _GeolocatorActiveState#6b088(lifecycle state: defunct)
E/flutter ( 4655): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
E/flutter ( 4655): The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
E/flutter ( 4655): This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().

CodePudding user response:

You can try what it suggests:

Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.

Like this:

if (mounted) {
    setState(() {
        long = position.longitude.toString();
        lat = position.latitude.toString();
    });
 }

CodePudding user response:

So there are two things wrong here:

  1. You call super.dispose outside of dispose method, which is not something you should do.
  2. You nullify _currentComposition stream first and then you try to cancel it, which is too late because you already lost access to that stream. You should switch the order.

By the way, I think you can easily put all stream disposal method inside dispose, rather than close them on button't onTap callback.

Here is your code example that I modified, notice overriden dispose method:

class _GeolocatorActiveState extends State<GeolocatorActive> {

  StreamSubscription<Position>? _currentPosition;

  double distanceToday = 0.0;
  double distanceTotal = 0.0;

  bool gpsActive = true;

  @override
  void initState() {
    super.initState();
    getCurrentPosition();
  }

  @override
  void dispose() {
    _currentPosition?.cancel();
    _currentPosition = null;
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
       // Some stuff
    ),
    ),
    backgroundColor: Colors.deepOrange
    ),
    body: Column(
      children: [
        // some stuff here
        TextButton(onPressed: () {
          setState(() {
            gpsActive = false;
          });
          Navigator.pushNamed(context, '/');},
          child: Text(
            'Finish',
          )
        ),
        // Some stuff there
      ],
    ),
    );
  }

  void getCurrentPosition () {
    final positionStream = GeolocatorPlatform.instance.getPositionStream();
    if(gpsActive == true){
      _currentPosition = positionStream.listen((position) {
        setState(() {
          long = position.longitude.toString();
          lat = position.latitude.toString();
        });
      });
    }
  }
}
  • Related