Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Tiles generated from AssetTileProvider is not removed from memory when Map disposed. #1424

Closed
5 tasks done
znromonk opened this issue Dec 29, 2022 · 4 comments
Closed
5 tasks done
Labels
bug This issue reports broken functionality or another error

Comments

@znromonk
Copy link

znromonk commented Dec 29, 2022

What is the bug?

When a map that uses AssetTileProvider is updated with new location, the memory is not released. If the location is updated, the allocated memory in the DevTools memory page keeps increasing.

I have tested it on iPad simulator with two columns, one with a list of locations, and another with the map. The three tabs have three distinct locations.

What is the expected behaviour?

Memory released when map/tile is disposed.

How can we reproduce this issue?

import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _selected = -1;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey.shade50,
      appBar: AppBar(
        backgroundColor: Colors.grey.shade50,
        foregroundColor: Colors.grey.shade900,
        title: const Text('Map Test'),
      ),
      body: SafeArea(
        child: Row(
          children: [
            Expanded(
              flex: 1,
              child: ListView.builder(
                itemCount: 3,
                itemBuilder: (context, index) {
                  return ListTile(
                    selected: _selected == index,
                    title: Text('Location $index'),
                    subtitle: const Text('Nice location'),
                    onTap: () {
                      setState(() {
                        _selected = index;
                      });
                    },
                    selectedColor: Colors.red,
                  );
                },
              ),
            ),
            Expanded(
              flex: 1,
              child: ListView(
                children: [
                  MapWidget(
                    location: LatLng(50, (-30.0 * _selected)),
                  ),
                  Text(
                    'Location $_selected',
                    style: Theme.of(context).textTheme.headline2,
                  ),
                  Text(
                    'Location description',
                    style: Theme.of(context).textTheme.bodyLarge,
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class MapWidget extends StatefulWidget {
  final LatLng location;
  const MapWidget({
    Key? key,
    required this.location,
  }) : super(key: key);

  @override
  State<MapWidget> createState() => _MapWidgetState();
}

class _MapWidgetState extends State<MapWidget> {
  final MapController _mapController = MapController();

  @override
  void dispose() {
    _mapController.dispose();
    super.dispose();
  }

  @override
  void didUpdateWidget(covariant MapWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    _mapController.move(widget.location, 5);
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 300,
      width: MediaQuery.of(context).size.width / 2,
      child: FlutterMap(
        mapController: _mapController,
        options: MapOptions(
          zoom: 5,
          interactiveFlags: InteractiveFlag.none,
          onMapReady: () {
            _mapController.move(widget.location, 5);
          },
        ),
        children: [
          TileLayer(
            urlTemplate: 'assets/{z}/{x}/{y}.png',
            tileProvider: AssetTileProvider(),
            tms: true,
            maxNativeZoom: 5,
          ),
          MarkerLayer(
            markers: [
              Marker(
                point: widget.location,
                builder: (context) {
                  return const Icon(Icons.location_city_rounded);
                },
              )
            ],
          )
        ],
      ),
    );
  }
}

ASSETS:
map.zip

Do you have a potential solution?

No response

Can you provide any other information?

fluttermap_memory_leak.mov

Platforms Affected

iOS

Severity

Obtrusive: Prevents normal functioning but causes no errors in the console

Frequency

Consistently: Always occurs at the same time and location

Requirements

  • I agree to follow this project's Code of Conduct
  • My Flutter/Dart installation is unaltered, and flutter doctor finds no relevant issues
  • I am using the latest stable version of this package
  • I have checked the FAQs section on the documentation website
  • I have checked for similar issues which may be duplicates
@znromonk znromonk added bug This issue reports broken functionality or another error needs triage This new bug report needs reproducing and prioritizing labels Dec 29, 2022
@TesteurManiak
Copy link
Collaborator

I've been able to reproduce the behavior with the provided code sample, but it might need further investigation to identify if it's an issue with the implementation of AssetTileProvider or with the way AssetImage is handled by Flutter.

@JaffaKetchup JaffaKetchup added non-fatal and removed needs triage This new bug report needs reproducing and prioritizing labels Dec 29, 2022
@znromonk
Copy link
Author

That's a good catch @TesteurManiak. There is an open issue (#62107) on the Flutter repo about image providers not releasing memory. I resolved the problem by adding these lines in the didUpdateWidget method.

    imageCache.clear();
    imageCache.clearLiveImages();

It is not the best solution since it clears image cache for all image providers used in the app but I was ok with it. I did notice that in debug mode the allocated memory didn't go up but the RSS did. I haven't done a profile test (since that involves registering the app on App Store Connect and all the certificate registration steps) to see if the RSS allocation is only for debugging.

I am not sure if this issue is to be kept open since there is a possibility the issue could be on the Flutter side.

On a side note, is there an alternate recommendation for loading offline map? The MBTiles provider code that was initially part of the flutter_map (#65 ) was taken out (flutter_map_mbtiles_provider) and hasn't been updated in a while.

@JaffaKetchup
Copy link
Member

Hey @znromonk, I think I can help with sideloading.
The newest information is available on the docs, but if you don't want to use file or asset providers, my plugin (mentioned there) supports sideloading.
Currently, it is very slow, but supports just adding a bunch of tiles manually (it's just a ZIP of a bunch of directories). After v7, it will be much faster (pretty much instant), but you'll have to use the bulk downloading & export functionality to generate the correct format. If you need more info, please do get in touch on Discord (#plugins) or in the Discussions section on my repo, and I'll be happy to help.

@znromonk
Copy link
Author

Thanks @JaffaKetchup. I will look into it. I think I will close the issue since the image cache issue is on the Flutter side and not this plugin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue reports broken functionality or another error
Projects
None yet
Development

No branches or pull requests

3 participants