mapbox / mapbox-maps-flutter

Interactive, thoroughly customizable maps for Flutter powered by Mapbox Maps SDK
https://www.mapbox.com/mobile-maps-sdk
Other
252 stars 94 forks source link

Memory leak if map is not visible #480

Open ristiisa opened 1 month ago

ristiisa commented 1 month ago

It seems that if the map is in an IndexedStack and is not visible, then some kind of a condition is triggered that allows the memory usage to grow until no more memory can be allocated and the app crashes.

Over the course of 114s the app allocated 10GB of memory:

image

Not for the same run, but I assume the details are the same:

image
import 'package:flutter/material.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  MapboxOptions.setAccessToken("<TOKEN HERE>");

  runApp(MaterialApp(home: MapSample()));
}

class MapSample extends StatefulWidget {
  @override
  State<MapSample> createState() => _MapSampleState();
}

class _MapSampleState extends State<MapSample> {
  final locationSettings = LocationComponentSettings(
    enabled: true,
    showAccuracyRing: true,
    pulsingEnabled: false,
    puckBearingEnabled: true,
    puckBearing: PuckBearing.HEADING
  );

  var lastLocation = Position(0, 0);

  Stream<Position>? location;

  @override
  initState() {
    super.initState();

    location = Stream.periodic(const Duration(seconds: 1), (_) {
      lastLocation = Position((lastLocation.lng + 1) % 180, (lastLocation.lat + 1) % 90);

      return lastLocation;
    });
  }

  @override
  Widget build(BuildContext context) => Scaffold(
    body: IndexedStack(index: 0, children: [
      Container(),
      StreamBuilder<Position>(stream: location, builder: (context, snapshot) {
        return MapWidget(
          key: ValueKey(lastLocation.toJson().toString()),
          cameraOptions: CameraOptions(center: Point(coordinates: snapshot.data ?? lastLocation), zoom: 4, pitch: 0.0),
          onMapCreated: (MapboxMap mapboxMap) async {
            await Future.wait([
              mapboxMap.scaleBar.updateSettings(ScaleBarSettings(enabled: false)),
              mapboxMap.gestures.updateSettings(GesturesSettings(rotateEnabled: false)),
              mapboxMap.attribution.updateSettings(AttributionSettings(clickable: false, iconColor: 1)),
              mapboxMap.location.updateSettings(locationSettings),
            ]);
          }
        );
      })
    ]));
}

https://github.com/ristiisa/mbmemleak

evil159 commented 1 month ago

Hi @ristiisa, thank you for the detailed report! I'm able to reproduce this behavior, created an issue in our internal issue tracker: https://mapbox.atlassian.net/browse/MAPSFLT-202

OdNairy commented 2 weeks ago

Hi @ristiisa This one looks like a bug on the Flutter side. As soon as we switch the index of the IndexedStack to show the MapView, extra allocations simply go away. Also according to the memory profiling, this is only the Flutter guts that hold extra MapView references. There is not much we can do to fix that on the Mapbox side, unfortunately 🙁 .

ristiisa commented 2 weeks ago

Hi @ristiisa This one looks like a bug on the Flutter side. As soon as we switch the index of the IndexedStack to show the MapView, extra allocations simply go away. Also according to the memory profiling, this is only the Flutter guts that hold extra MapView references. There is not much we can do to fix that on the Mapbox side, unfortunately 🙁 .

That is unfortunate... Have you discovered a workaround?

ristiisa commented 2 weeks ago

To be honest we actually stopped using this library due to the issues we were having and I created our own streamlined wrapper over native Mapbox SDK.

We have a tabbed interface with several maps on screen at once per tab. Since map initialisation causes some "flicker" I designed our wrapper so that the native view could be always"recycled". So after going over our code real quick I can verify that the memory leak is present for our implementation aswell with the sample code provided. However since we recycle the maps the key for the map is a global one and thus it will not be initialised with every rebuild.

So I guess the workaround would be (if applicable):

  1. limit widget initialisations
  2. if the map is not visible disable updates

I'll try to figure out a repro code without Mapbox and submit a issue to flutter team.

ristiisa commented 1 week ago

for future reference: https://github.com/flutter/flutter/issues/148639

artemsorochan commented 2 days ago

FYI, I have similar issue(but rather fps drop), and for me disabling location via LocationComponentSettings(enabled: false...) fixes it.. Android only