StreamBuilder doesn't rebuild after Navigator.pop

Viewed 844

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 !

1 Answers

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
        },
    );
    ...
}
Related