StreamBuilder doesn't rebuild after Navigator.pop

754

When you push and after pop a page, the build metod doesn't restart. I found the same problem with the FlutterBluetoothSerial.instance.onStateChanged() stream and the solution that I found is to add the stream to a local static final variable and use it instead of calling every time the original method (you can do that only if the stream is a broadcast one I think).

Solution example:

class ExampleClass {
    static final Stream<LatLng> locationStream = LocationService().locationStream;
}

class SelfUpdatingMap extends StatelessWidget {
    ...
    @override
    Widget build(BuildContext context) => StreamBuilder<LatLng>(
        stream: ExampleClass.locationStream,
        builder: (context, asyncSnapshot) {
            if (asyncSnapshot.hasError || asyncSnapshot.data == null) {
                return Text('Loading...');
            }
            try {
                _controller?.move(asyncSnapshot.data, 18);
            } catch (ignored) {}
            return _createMapWidget(context, asyncSnapshot.data);
        },
    );
    ...
}

class Page3Widget extends StatelessWidget {
    ...
    @override
    Widget build(BuildContext context) => StreamBuilder<LatLng>(
        stream: ExampleClass.locationStream,
        builder: (context, asyncSnapshot) {
            //do something
        },
    );
    ...
}
Share:
754
Skyost
Author by

Skyost

Me My IRL name is Hugo Delaunay (it's been more than ten years now that I'm using "Skyost" on the internet) and I'm an amateur developer. My skills It's been some years now that I develop computer programs. I'm also developing some apps for Android and iOS devices. I'm sometimes working on web applications. My projects You will find below two personal projects that I really want to share with you. Mobile Werewolf : Mobile Werewolf is an unofficial mobile version of the famous board game Mafia. The concept is simple : you and your friends are the inhabitants of a strange village where some of you turn into nasty werewolves at night. Bacomathiques : "Bac-o-math-iques" (or just Bacomathiques) is a small app (but also a website) that contains everything you need to revise maths in the french scholar system from the Première to the Terminale ! If you need pass an exam or you just want to revise your lesson : everything is possible and everything is free. If you want to see more projects, feel free to check out my Github profile. Contact me If you have any question feel free to contact me.

Updated on December 07, 2022

Comments

  • Skyost
    Skyost over 1 year

    I have a simple service which is tracking the current user position :

    class LocationService {
      LatLng _lastLocation;
      Location location = Location();
    
     StreamController<LatLng> _locationController = StreamController<LatLng>();
     Stream<LatLng> get locationStream => _locationController.stream;
    
      LocationService() {
        location.onLocationChanged().listen((locationData) {
          LatLng location = LatLng(locationData.latitude, locationData.longitude);
          if(_lastLocation == null || _lastLocation != location) {
            _lastLocation = location;
            _locationController.add(location);
          }
        });
      }
    }
    

    Then, I'm using this service to create a Map (thanks to flutter_map) which is following the current user position :

    class SelfUpdatingMap extends StatelessWidget {
      final Icon currentPositionIcon;
    
      final MapController _controller = MapController();
    
      SelfUpdatingMap({
        this.currentPositionIcon,
      });
    
      @override
      Widget build(BuildContext context) => StreamBuilder<LatLng>(
            stream: LocationService().locationStream,
            builder: (context, asyncSnapshot) {
              if (asyncSnapshot.hasError || asyncSnapshot.data == null) {
                return Text('Loading...');
              }
    
              try {
                _controller?.move(asyncSnapshot.data, 18);
              } catch (ignored) {}
              return _createMapWidget(context, asyncSnapshot.data);
            },
          );
    
      Widget _createMapWidget(BuildContext context, LatLng location) => FlutterMap(
            options: MapOptions(
              center: location,
              zoom: 18,
            ),
            layers: [
              TileLayerOptions(
                urlTemplate: 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png', // https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png is good too.
                subdomains: ['a', 'b', 'c'],
              ),
              MarkerLayerOptions(
                markers: [
                  Marker(
                      width: 40,
                      height: 40,
                      point: location,
                      builder: (contact) => currentPositionIcon,
                    ),
                ]
              ),
            ],
            mapController: _controller,
          );
    }
    

    Then, I use the SelfUpdating widget in two places :

    • The page 1, ancestor of page 2.
    • And in the page 3, successor of page 2.

    So here is the situation :

    1. I launch my app, I'm on the page 1. I have my SelfUpdatingMap.
    2. I call Navigator.pushNamed(context, '/page-2').
    3. I call Navigator.pushNamed(context, '/page-3'). I have another SelfUpdatingMap.
    4. I call two times Navigator.pop(context), I get the page 1 BUT the SelfUpdatingMap doesn't update itself anymore.

    The builder is not even called anymore. So please, what is wrong with this code ?

    Thank you !

    • pskink
      pskink almost 5 years
      try extending StatefulWidget and init your LocationService inside initState method, also try to override deactivate / dispose to release the resources you init in your custom State
    • Skyost
      Skyost almost 5 years
      @pskink That's what I used to do before using a StreamBuilder. Sadly, it's the same result.