dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.21k stars 1.75k forks source link

I notice Android Map handler MapPins() method gets called twice each time I add to the Pins Collection. #15570

Open davefxy opened 1 year ago

davefxy commented 1 year ago

Description

When using Android an app with a custom Map handler, I noticed the Map handler MapPins() method is being called twice whenever I modify the Pins collection. I was able to verify this using the maui\src\Controls\samples\Controls.Sample project on an android device after setting a breakpoint at the MapPins() method in the maui MapHandler.Android.cs.

Steps to Reproduce

  1. I downloaded and compiled the dotnet\maui repository per the Development.md steps "Compile using a local bin\dotnet".
  2. After VS 2022 started, I ran the maui\src\Controls\samples\Controls.Sample project on an Android device after setting a breakpoint at the MapPins() method in the maui MapHandler.Android.cs. Each time I added a Pin using the "Add" button in the Controls/Maps/PinItemsSourceGallery, MapPins() was called twice for each added pin.
  3. Following the code through src\Controls\Maps\Map.cs, the OnItemsSourceCollectionChanged() method is called. In this method the e.Apply(insert: (item, _, __) => CreatePin(item)) is executed. This causes a NotifyCollectionChangedAction Apply() which calls PinsOnCollectionChanged(), and then Handler?.UpdateValue(nameof(IMap.Pins)) => MapPins(). Execution returns to OnItemsSourceCollectionChanged() to the line : Handler?.UpdateValue(nameof(IMap.Pins) => MapPins(), 2nd time this is called for each Pin collection change.
  4. And this is why MapPins() is being called twice for simply adding a Pin. I don't think the code was the intended to follow this path. Perhaps there should be a check here to prevent Handler?.UpdateValue(nameof(IMap.Pins) from being called twice, once in PinsOnCollectionChanged and then again in OnItemsSourceCollectionChanged()?

Link to public reproduction project repository

https://github.com/dotnet/maui

Version with bug

7.0.49

Last version that worked well

6.0

Affected platforms

Android

Affected platform versions

Android 32

Did you find any workaround?

no

Relevant log output

No response

ghost commented 1 year ago

We've added this issue to our backlog, and we will work to address it as time and resources allow. If you have any additional information or questions about this issue, please leave a comment. For additional info about issue management, please read our Triage Process.

davefxy commented 1 year ago

This does appear to be a performance issue. But I was looking at https://github.com/VladislavAntonyuk/MauiSamples/MauiMaps sample because I want to replace the map icon with custom icons(i.e. images). I want to use his Android CustomMapHandler. The iOS version seems to work fine. Perhaps you can point out how I can change the Android CustomMapHandler to work as expected. Here are the 2 methods affected: ` private static new void MapPins(IMapHandler handler, IMap map) { if (handler is CustomMapHandler mapHandler) { foreach (var marker in mapHandler.Markers) { marker.Remove(); }

        mapHandler.AddPins(map.Pins);
    }
}

private void AddPins(IEnumerable<IMapPin> mapPins)
{
    if (Map is null || MauiContext is null)
    {
        return;
    }

    foreach (var pin in mapPins)
    {
        var pinHandler = pin.ToHandler(MauiContext);
        if (pinHandler is IMapPinHandler mapPinHandler)
        {
            var markerOption = mapPinHandler.PlatformView;
            if (pin is CustomPin cp)
            {
                cp.ImageSource.LoadImage(MauiContext, result =>
                {
                    if (result?.Value is BitmapDrawable bitmapDrawable)
                    {
                        markerOption.SetIcon(BitmapDescriptorFactory.FromBitmap(bitmapDrawable.Bitmap));
                    }

                    AddMarker(Map, pin, Markers, markerOption);
                });
            }
            else
            {
                AddMarker(Map, pin, Markers, markerOption);
            }
        }
    }
}

private static void AddMarker(GoogleMap map, IMapPin pin, ICollection<Marker> markers, MarkerOptions markerOption)
{
    var marker = map.AddMarker(markerOption);
    pin.MarkerId = marker.Id;
    markers.Add(marker);
}

` A problem is the order in which these methods are performed. Here is an example: MauiMap sample app is started. The user selects the Add location button.

  1. The app adds a location to a ObservableCollection of custompins (i.e. Pin with an ImageSource for the pin.).
  2. CustomMapHandler MapPins() method is called. It removes each of the existing list of Markers, then calls the AddPins() method which queues up LoadImage() method for each new pin.
  3. CustomMapHandler MapPins() method is called a 2nd time. It removes each of the existing list of Markers, then calls the AddPins() method which queues up LoadImage() method for each new pin.
  4. Execution returns to the app
  5. All of the queued up LoadImage() from the 2 MapPins() requests are now performed. Adding a new marker for each of the newly added pins for both MapPins() calls. MapPins() is expecting to remove each of the Markers in the list. However the markers aren't added until after the thread returns to the app. The 2nd time MapPins() is performed, it is before the Marker list is updated by the queued LoadImage() requests. As a result there are an increasing number of duplicate Markers created each time a Add new pin is added. Can the AddPins() method's LoadImage() calls not be queued or can these queued calls be performed before the 2nd call to MapPins() ? Or perhaps I could set a flag as these requests are made and reset it when they are completed. I could prevent the processing the 2nd MapPins call.
davefxy commented 1 year ago

I added all of the code between the single quotes. Why doesn't all the code appear in the same code section?

NKJRao commented 10 months ago

Hi davefxy, I want to add two makers, one from google map and another one from Maui maps.(like xamarin forms??) I have used the same code in my map handler, Where we are adding marker in google map and can showing the image at pin, is there any option we can add marker in Maui maps as well like google maps (as you done above.)

NKJRao commented 10 months ago

I added all of the code between the single quotes. Why doesn't all the code appear in the same code section? Hi davefxy, I want to add two makers, one from google map and another one from Maui maps.(like xamarin forms??) I have used the same code in my map handler, Where we are adding marker in google map and can showing the image at pin, is there any option we can add marker in Maui maps as well like google maps (as you done above.)

NKJRao commented 10 months ago

Here I want to show a car moving, the current pin showing the car image, and the previous pin showing the arrow image where the car already moved like a path moved by a car. I hope you understand..

davefxy commented 10 months ago

Look at the repository : https://github.com/VladislavAntonyuk/MauiSamples, specifically the https://github.com/VladislavAntonyuk/MauiSamples/tree/main/MauiMaps sample. This sample will demonstrate how to use custom pin images in Maui.

NKJRao commented 9 months ago

Thanks for the information, I have already used the above code and made my Maui handler accordingly but I'm stuck at adding the two markers, my code is attached with the screen where I want the marker. currently, I can only add the car icon but not arrow icons for previous pins. I am not sure how to add that, in Xamarin i was using likewise(one marker showing on Xamarin map and another on Google Maps.)

Xamarin custom map renderer:

` public new Android.Gms.Maps.GoogleMap Map { get; set; }

protected override MarkerOptions CreateMarker(Pin pin) { MarkerOptions marker = new MarkerOptions(); MarkerOptions marker2 = new MarkerOptions();

 try
 {
     marker.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
     marker.SetTitle(pin.Label);
     marker.SetSnippet(pin.Address);
     marker2.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
     marker2.SetTitle(pin.Label);
     marker2.SetSnippet(pin.Address);
     CustomPin CustPin = new CustomPin();
     CustPin = (CustomPin)pin;
     marker.SetRotation(CustPin.Rotation + 90);
     marker2.SetRotation(CustPin.Rotation + 180);
     OSAppTheme currentTheme = Xamarin.Forms.Application.Current.RequestedTheme;

     if (CustPin.Ignition == 0 & currentTheme == OSAppTheme.Dark)
     {
         marker.SetIcon(BitmapDescriptorFactory.FromAsset("car_icon_3.png"));
         marker2.SetIcon(BitmapDescriptorFactory.FromAsset("darkMode.png"));
     }
     else if (CustPin.Movement == 0 && CustPin.Ignition == 0 && CustPin.PreviousIgnition == 0)  // Parked
     {
         marker.SetIcon(BitmapDescriptorFactory.FromAsset("car_icon.png"));
         marker2.SetIcon(BitmapDescriptorFactory.FromAsset("Parked.png"));
     }
     else if (CustPin.Ignition == 1 && CustPin.PreviousIgnition == 0)//Ignition Off to on
     {
         marker.SetIcon(BitmapDescriptorFactory.FromAsset("car_icon_2.png"));
         marker2.SetIcon(BitmapDescriptorFactory.FromAsset("Inition_on.png"));
         marker2.SetRotation(CustPin.Rotation = 0);
     }
     else if (CustPin.Ignition == 0 && CustPin.PreviousIgnition == 1)//Ignition On to off
     {
         marker.SetIcon(BitmapDescriptorFactory.FromAsset("car_Inition_off.png"));
         marker2.SetIcon(BitmapDescriptorFactory.FromAsset("Inition_off.png"));
         marker2.SetRotation(CustPin.Rotation = 0);
     }
     else if (CustPin.Movement == 0 && CustPin.Ignition == 1)//Idel
     {
         marker.SetIcon(BitmapDescriptorFactory.FromAsset("Idle_car.png"));
         marker2.SetIcon(BitmapDescriptorFactory.FromAsset("Idle.png"));
     }
     else if (CustPin.Movement == 1 && CustPin.Ignition == 1 && Convert.ToInt32(ReplayNew.Sspd) < Utility.GetNumberFromString(CustPin.speed)) //Overspeed
     {
         marker.SetIcon(BitmapDescriptorFactory.FromAsset("car_icon_4.png"));
         marker2.SetIcon(BitmapDescriptorFactory.FromAsset("Overspeed.png"));
     }
     else if (CustPin.Movement == 1 && CustPin.Ignition == 1 && Convert.ToInt32(ReplayNew.Sspd) > Utility.GetNumberFromString(CustPin.speed))
     {
         marker.SetIcon(BitmapDescriptorFactory.FromAsset("car_icon_1.png"));
         marker2.SetIcon(BitmapDescriptorFactory.FromAsset("Moving.png"));
     }
     else
     {
         //marker.SetIcon(BitmapDescriptorFactory.FromAsset("car_icon_1.png"));
         //marker2.SetIcon(BitmapDescriptorFactory.FromAsset("Moving.png"));
         marker.SetIcon(BitmapDescriptorFactory.FromAsset("car_icon.png"));
         marker2.SetIcon(BitmapDescriptorFactory.FromAsset("Parked.png"));
     }
     Map.AddMarker(marker2);

 }
 catch (Exception ex)
 {
     LogManager.Log(ex);
 }
 return marker;

} ` Please find the attached handler code for MAUI Mapsin maui.txt map

My handler code is attached for maui

NKJRao commented 9 months ago

Hi, Can anyone help me, with the above, I'm stuck on how can I add two markers in Maui