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

disable map reload #5

Open
rahmanrezaee opened this issue Jun 17, 2022 · 3 comments
Open

disable map reload #5

rahmanrezaee opened this issue Jun 17, 2022 · 3 comments

Comments

@rahmanrezaee
Copy link

reload I think is so bad UX for user

an example is for test data which build it in if we get data from the server and wait for data and reload the map takes many seconds.
please find a right way to implement this issue

@theo-michel
Copy link

Yes same issue here, would be cool if we could do live location updates, like Uber. Maybe process all the widget things in the background ? (Not sure that is possible in flutter) Or maybe once markers are added make it so we can move them without reloading ?
But great work still I am gonna try to contribute if I have the time !

@Djcharles26
Copy link

I believe that for the nature of builders, the reload of the map itself cannot be disabled, but, you can use their own classes of custom markers and markers controller to generate your own custom markers in the state and just pass them to the map

Here my code:

GoogleMapController? _mapController;
List<Location> locations = [];
late MarkersController markersController;
Set<Marker> markers = {};

void markersControllerListener () {
   // This listener only will be called when all the widgets are converted to images
    if (markersController.images != null) {
      try {
        setState(() {
          markers = locations.map<Marker> (
          (location) => Marker (
            markerId: MarkerId (location.id),
            position: LatLng (location.lat, location.lon),
            onTap: () => widget.openStore (location.id),
            icon: BitmapDescriptor.fromBytes(markersController.images! [locations.indexOf(location)])
          )
          ).toSet();
        });
      } catch (error) {
        showErrorDialog(
          context,
          title: S.of(context).error,
          message: S.of(context).anErrorOccurredLoadingMarkers
        );
      }
    }
  }

  // Call this function in your initState
  void _loadLocationMarkers () async {
    if (mounted) {
      locations = Provider.of<Locations> (context, listen: false).locations;
    }

    markersController = MarkersController (
      value: List<Uint8List?>.filled(locations.length, null),
      childCount: locations.length
    );

    markersController.addListener(markersControllerListener);
  }
  
@override
  Widget build(BuildContext context) {
    return Stack(
      children: [
            // Add A generated list of custom markers in the tree of your app to capture the images and convert them to pngs to be added as bytes in the google marker
        ...(
            markersController.ready
            ? []
            : List.generate(
              markersController.childCount,
              (index) => Positioned(
              left: -MediaQuery.of(context).size.width,
              child: CustomMarker(
                child: locations[index].record.type.marker , // Here i have an enum with a widget called marker
                screenshotDelay: Duration.zero, // Remove delay
                onImageCaptured: (data) {
                  markersController.updateRenderedImage(index, data);
                },
              ),
            )
          )
        ),
        GoogleMap(
          myLocationEnabled: runtime != "Development",
          myLocationButtonEnabled: false,
          tiltGesturesEnabled: false,
          zoomControlsEnabled: false,
          mapToolbarEnabled: false,
          mapType: MapType.normal,
          rotateGesturesEnabled: false,
          buildingsEnabled: false,
          minMaxZoomPreference: const MinMaxZoomPreference(
            0, 20
          ),
          onMapCreated: (controller) async {
           ...
          },  
          markers: markers,
        ),
      ],
    );
  }

As a note, I had to extract markers controller from the package cause it is private and custom markers you can use the model from the package located in /src folder.

Good Luck!

@theo-michel
Copy link

I have used the code from @Djcharles26 to try to add Widget containing markers to the map on click of a fab. ^
Hope this helps ^^

import 'dart:typed_data';

import 'package:flutter/cupertino.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:collection/collection.dart';
import 'dart:ui' as ui;
import 'package:synchronized/synchronized.dart';

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

  @override
  State<MapOptiTemp> createState() => _MapOptiTempState();
}

class _MapOptiTempState extends State<MapOptiTemp> {
  // GoogleMapController? _mapController;
  List<Point> locations = [];
  late MarkersController markersController;
  Set<Marker> markers = {};
  double latitude = 0.0;
  double longitude = 0.0;

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

  void markersControllerListener() {
    // This listener only will be called when all the widgets are converted to images
    if (markersController.images != null) {
      try {
        setState(() {
          markers = locations
              .map<Marker>((point) => Marker(
                  markerId: MarkerId(point.id),
                  position: LatLng(point.lat, point.long),
                  onTap: () {},
                  icon: BitmapDescriptor.fromBytes(
                      markersController.images![locations.indexOf(point)])))
              .toSet();
        });
      } catch (error) {
        print("Error custom Map: $error");
      }
    }
  }

// Call this function in your initState
  void _loadLocationMarkers() async {
    if (mounted) {
      locations = [
        Point("init point 1", 45.413811, 6.991759),
        Point("init point 2", 45.414698, 6.988807)
      ];
    }
    markersController = MarkersController(
        value: List<Uint8List?>.filled(locations.length, null),
        childCount: locations.length);

    markersController.addListener(markersControllerListener);
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        // Add A generated list of custom markers in the tree of your app to capture the images and convert them to pngs to be added as bytes in the google marker
        ...(markersController.ready
            ? []
            : List.generate(
                markersController.childCount,
                (index) => Positioned(
                      left: -MediaQuery.of(context).size.width,
                      child: CustomMarker(
                        child: Icon(Icons.account_circle),
                        screenshotDelay: Duration.zero, // Remove delay
                        onImageCaptured: (data) {
                          markersController.updateRenderedImage(index, data);
                        },
                      ),
                    ))),

        GoogleMap(
          initialCameraPosition: CameraPosition(
              target: LatLng(45.449320496612295, 6.976185903919156)),
          myLocationButtonEnabled: false,
          tiltGesturesEnabled: false,
          zoomControlsEnabled: false,
          mapToolbarEnabled: false,
          mapType: MapType.hybrid,
          rotateGesturesEnabled: false,
          buildingsEnabled: false,
          onCameraMove: (object) {
            print("Markers : $markers");
            latitude = object.target.latitude;
            longitude = object.target.longitude;
          },
          minMaxZoomPreference: const MinMaxZoomPreference(0, 20),
          onMapCreated: (controller) async {},
          markers: markers,
        ),
        Positioned(
            bottom: 16,
            left: 0,
            right: 0,

            child: FloatingActionButton(
              child: Icon(Icons.add),
                onPressed: () async {
              print("latitude $latitude");
              print("longitude $longitude");
              locations.add(Point("New point", latitude, longitude));
              markersController.prepareAddWidget();
              markersController.childCount = locations.length;
              setState(() {});
            }))
      ],
    );
  }
}

class Point {
  String id = "";
  double lat = 0.0;
  double long = 0.0;

  Point(this.id, this.lat, this.long);
}

/// [MarkersController] handles the state of rendered markers and notify
/// listeners when all marker are rendered and captured.

class MarkersController extends ValueNotifier<List<Uint8List?>> {
  int childCount = 0;

  List<Uint8List?> renderedWidgets = List.empty(growable: true);

  MarkersController({required List<Uint8List?> value, required this.childCount})
      : super(value) {
    renderedWidgets = List<Uint8List?>.filled(childCount, null);
  }

  updateRenderedImage(int index, Uint8List? data) {
    renderedWidgets[index] = data;
    if (ready) {
      value = List.from(renderedWidgets);
    }
  }

  bool get ready => !renderedWidgets.any((image) => image == null);

  void prepareAddWidget() {
    renderedWidgets = renderedWidgets.toList();
    renderedWidgets.add(null);
    childCount++;
  }

  List<Uint8List>? get images => ready ? value.cast<Uint8List>() : null;
}

////////////////CUSTOM MARKER//////////////////////

/// a widgets that capture a png screenshot of its content and pass it through
/// [onImageCaptured].
class CustomMarker extends StatefulWidget {
  final Widget child;
  final Function(Uint8List?)? onImageCaptured;
  final Duration? screenshotDelay;

  const CustomMarker(
      {Key? key,
      required this.child,
      this.onImageCaptured,
      this.screenshotDelay})
      : super(key: key);

  @override
  _CustomMarkerState createState() => _CustomMarkerState();
}

class _CustomMarkerState extends State<CustomMarker> {
  final GlobalKey key = GlobalKey();
  final Function eq = const ListEquality().equals;
  Uint8List? _lastImage;
  final lock = Lock();

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

  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      lock.synchronized(() async {
        await Future.delayed(
            widget.screenshotDelay ?? const Duration(milliseconds: 500));
        final _image = await _capturePng(key);
        if (_lastImage == null || !eq(_lastImage!, _image)) {
          _lastImage = _image;
          widget.onImageCaptured?.call(_image);
        } else {
          widget.onImageCaptured?.call(_lastImage);
        }
      });
    });
    return RepaintBoundary(
      key: key,
      child: widget.child,
    );
  }

  Future<Uint8List?> _capturePng(GlobalKey iconKey) async {
    try {
      final RenderRepaintBoundary? boundary =
          iconKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;
      if (kDebugMode && (boundary?.debugNeedsPaint ?? false)) {
        await Future.delayed(const Duration(milliseconds: 200));
        return _capturePng(iconKey);
      }
      ui.Image? image = await boundary?.toImage(pixelRatio: 3.0);
      ByteData? byteData =
          await image?.toByteData(format: ui.ImageByteFormat.png);
      var pngBytes = byteData?.buffer.asUint8List();
      return pngBytes;
    } catch (e) {
      return null;
    }
  }
}

KishanBusa8 added a commit to KishanBusa8/custom_map_markers that referenced this issue Sep 20, 2022
- Solved marker blinking issue every time when map reloads or when we add a marker to the existing list or remove or update the marker list.
issue related to IbrahimTabba#5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants