liodali / osm_flutter

OpenStreetMap plugin for flutter
https://pub.dev/packages/flutter_osm_plugin
MIT License
234 stars 97 forks source link

!debugNeedsPaint when using a map in the IndexedStack widget #508

Open tmp78 opened 7 months ago

tmp78 commented 7 months ago

I'm not sure if this is a bug or if I'm using the map in a way that wasn't intended. I am using flutter_osm_plugin in a tab (not the first one) in IndexedStack. So when I try to show Geopoints on the map at application startup I get an error !debugNeedsPaint. I dealt with this by running the function showing Geopoints only after clicking the tab with the map. But if I click on another tab while rendering GeoPoints, the error also appears. Error:

E/flutter (10780): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: 'package:flutter/src/rendering/proxy_box.dart': Failed assertion: line 3356 pos 12: '!debugNeedsPaint': is not true.
E/flutter (10780): #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
E/flutter (10780): #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
E/flutter (10780): #2      RenderRepaintBoundary.toImage (package:flutter/src/rendering/proxy_box.dart:3356:12)
E/flutter (10780): #3      MethodChannelOSM._capturePng (package:flutter_osm_interface/src/channel/osm_method_channel.dart:341:30)
E/flutter (10780): #4      MethodChannelOSM.addMarker (package:flutter_osm_interface/src/channel/osm_method_channel.dart:549:24)
E/flutter (10780): #5      MobileOSMController.addMarker.<anonymous closure> (package:flutter_osm_plugin/src/controller/osm/osm_controller.dart:485:27)
E/flutter (10780): #6      new Future.delayed.<anonymous closure> (dart:async/future.dart:427:39)
E/flutter (10780): #7      Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
E/flutter (10780): #8      _Timer._runTimers (dart:isolate-patch/timer_impl.dart:398:19)
E/flutter (10780): #9      _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:429:5)
E/flutter (10780): #10     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)

I have a solution for this, maybe it will be useful for someone who has a similar way of using maps. I call the function that draws GeoPoints in the try catch block:

void setItemsOnMap(List<Item> itemsList) async {
    if (itemsList.isNotEmpty && _controller != null) {
      try {
        _items.addAll(itemsList);
        for (Item item in itemsList) {
          if (item.latitude != null && item.longitude != null) {
            GeoPoint geoPoint =
                GeoPoint(latitude: item.latitude!, longitude: item.longitude!);
            _geoPoints.add(geoPoint);
            await _controller!.addMarker(geoPoint,
                markerIcon: MarkerIcon(
                  iconWidget: SomeWidgetWithText(item.number.toString())
                ),
                iconAnchor: IconAnchor(
                  anchor: Anchor.top,
                ));
          }
        }
        await zoomToGeoPoints();
        _paintError = false;
      } catch (e) {
        debugPrint('Error while setting items on the map: $e');
        _paintError = true;
      }
      notifyListeners();
    }
  }

And then I check the variable _paintError in GoRouter:

GoRoute(
          name: 'home',
          path: '/:tab',
          builder: (context, state) {
            final tab = int.tryParse(state.pathParameters['tab'] ?? '') ?? 0;
            if (tab == 3 &&
                mapManager.paintError &&
                appStateManager.isMapPreviouslyOpened) {
              mapManager.setItemsOnMap(playerManager.itemsList);
            }
            return Home(key: state.pageKey, currentTab: tab);
          }

So, basically it works, trying to render the GeoPoints on the map until the user stays on the tab long enough to succeed, but maybe there is an easier way to handle this?

Additionally, I have a few more observations:

  1. Marker icons and road width are much smaller on Android than on iOS. I deal with it simply: size: Platform.isAndroid ? 84 : 40. But should it be so? Interestingly, this does not apply to markers added via the controller.addMarker.
  2. An icon defined in directionArrowMarker seems to have no effect on iOS (the icon does not change from the one defined in the personMarker). I can't test it on Android, because I am having the problems described in this issue.
  3. Marker icons with text on them are blurry on Android, , but clear on iOS.
  4. Is there any way to read the degree of map rotation? It would be useful to display this information to the user.

I'm using Flutter 3.19.3 (3.16.9 before, with the same result) and flutter_osm_plugin 0.70.4.

liodali commented 7 months ago

for tabs you need to prevent re render the tab body means keep the state but give some time to test it and i have question on which platform to get that error ? and for directionArrowMarkerare you testing with simulator or iphone ?

tmp78 commented 7 months ago

This error occurs on both Android and iOS. Testing only on simulators (iOS 17.4, Android 12) on Mac M1.

liodali commented 7 months ago

when you navigate to other tabs are you trying to add markers to the map or just when you change the tab the error appears ?

tmp78 commented 7 months ago

I run the function setItemsOnMap() the first time I click on the map tab. Then I have to wait until the map starts and the markers are generated. Otherwise, the mentioned error appears. After generating the markers, I can change the tabs, the error does not appear.

liodali commented 7 months ago

can you give me some information about those markers like they are location or markt or taxis similar to that because we can static position where you can use them if you can prepare your markers even before switch to the map and when the user switch to the tab map they will appear with map and you can change them also

tmp78 commented 7 months ago

Generally, it all works the way I want it to. I just wanted to inform you about this bug and that adding markers to the map in IndexedStack requires additional steps. My markers are static, but i need a separate icon for each marker, for example:

Zrzut ekranu 2024-03-10 o 14 35 05

Preparing markers before switching to map would be even better, but is this possible with individual icon for each marker? If so, how to do it?

liodali commented 7 months ago

as we discuss in #507 I have some question for directionArrowIconbecause i tested on real device it show when angle != 0 you have specific use case where I need to activate directionIcon ?

tmp78 commented 7 months ago

@liodali, you asked about directionArrowMarker. I've tested it only on the Android and iOS simulator. Code:

OSMOption(
        //some code
        userLocationMarker: UserLocationMaker(
            personMarker: MarkerIcon(
              icon: Icon(
                Icons.location_history,
                color: const Color(0xFF46664C),
                size: Platform.isAndroid ? 84 : 40,
              ),
            ),
            directionArrowMarker: MarkerIcon(
              icon: Icon(Icons.navigation_rounded,
                  color: const Color(0xFF46664C),
                  size: Platform.isAndroid ? 84 : 40),
            )),
      //some code
      ),

And then:

await _controller?.enableTracking(
        enableStopFollow: true,
        disableUserMarkerRotation: false,
        anchor: Anchor.center,
      );

The result on the Android simulator: simulator-android

The result on the iOS simulator: Simulator-iphone

Changing from disableUserMarkerRotation: false to disableUserMarkerRotation: true has no effect. On the iPhone simulator there is always icon defined in personMarker that never rotates, on the Android simulator - icon defined in directionArrowMarker that always rotates. I'm using 1.0.0-rc.6 version. If I remember correctly, on the stable version, changing the disableUserMarkerRotation had an effect on Android, but not on iOS.

liodali commented 7 months ago

i will add new api in tracking to show directionIcon if you want when start tracking or i will see how i can detect fast changing of location where i can switch to directionMarker the map orientation has no effect on rotation of user marker but i will add that feature in the stable version

tmp78 commented 7 months ago

No, I don't need a new API. A navigation feature is not the main feature in my app:-). I'm just curious. Well, I thought it was a mistake. The documentation includes the following: "to disable rotation of user marker, change disableUserMarkerRotation to true". It doesn't matter whether I set this property to "true" or "false"- on the Android simulator a marker always rotates as the user moves and turns and on the iOS simulator user marker never rotates. Once again, here's what it looks like on Android device: android2

On the iOS device - it looks as if there was no detection of the user's change of direction. The marker never rotates, when the user turns. The user speed has no impact- I tested it at different speeds (run, bike ride and drive on iOS and 1x - 5x speed on Android).

liodali commented 7 months ago

I want to applogize because I was mistaken and you right for disableRotateDirection is not implemented as expected in android side after fixing the latest bug since I changed the implementation I will make fix for it

liodali commented 7 months ago

i published new version contain new feature where you can now get user location without our logic to control the map which will give the other developer to manipulate the map more freely

tmp78 commented 6 months ago

Sorry, I didn't have time to test it, but now, after the update, I have the same problems as described here: #519 . Any hints, how to use controller.startLocationUpdating() method?

liodali commented 6 months ago

the startLocationUpdating and stopLocationUpdating those for third party user location tracking it means that you will only get the user location without our logic that means the developer has the freedoom to control the map while getting the user location

tmp78 commented 6 months ago

OK, I understand that, but I don't know how to start... How to control the map? In the onLocationChanged method?

But that's not so important. I found another bug on iOS. My way of dealing with the !debugNeedsPaint error resulted in duplicate geopoints. Something I didn't notice before. Solution - remove geopoints before calling the setItemsOnMap method again:

List<GeoPoint> doubledGeoPoints = await controller.geopoints;
    try {
      for (GeoPoint geoPoint in doubledGeoPoints) {
        debugPrint('removing ${geoPoint.longitude}, ${geoPoint.latitude}');
        await controller.removeMarker(geoPoint);
      }
    } catch (e) {
      debugPrint('Error while removing items from the map: $e');
    }

This works fine on Android, but causes the app to crash on iOS without any warning.

flutter: removing 19.35075, 52.23779
flutter: removing 19.34705, 52.23549
flutter: removing 19.35921, 52.23779
flutter: removing 19.36559, 52.23392
flutter: removing 19.36281, 52.2308
flutter: removing 19.36174, 52.23072
flutter: removing 19.36921, 52.22925
flutter: removing 19.36572, 52.22149
Lost connection to device.
Error: Unable to terminate [...]:
ProcessException: Process exited abnormally with exit code -2:

  Command: /usr/bin/arch -arm64e xcrun simctl terminate [...]
the Dart compiler exited unexpectedly.

Tested on iOS simulator.

liodali commented 6 months ago

await in for will not work as expected we didnt have a remove marker api with list of markers ????

tmp78 commented 6 months ago

@liodali, are you asking me? I don't know:-) However, I found another method: removeMarkers(List geoPoints). In osm_controller.dart it is described as:

/// this method will delete list of markers, even if the markers not exist will be skipped

However, using this method has the same result, as using removeMarker(geoPoint) method. Works on Android and crush the app on iOS. But, at least we know, that it's not the for loop's fault.

In osm_map.swift there is function deleteMarkers:

func deleteMarkers(call:FlutterMethodCall){
        let geoPoints = (call.arguments as! [GeoPoint]).map { point -> CLLocationCoordinate2D in
            point.toLocationCoordinate()
        }
        geoPoints.forEach { location in
            self.mapOSM.markerManager.removeMarker(location: location)
        }
    }
liodali commented 6 months ago

i will check it why it didnt work because it should.work

tmp78 commented 6 months ago

Maybe this will help a little. Log:

Last Exception Backtrace:
0   CoreFoundation                         0x10a93b561 __exceptionPreprocess + 226
1   libobjc.A.dylib                        0x10a0167e8 objc_exception_throw + 48
2   CoreFoundation                         0x10a9c4d57 _CFGetHandleForLoadedLibrary.cold.1 + 0
3   CoreFoundation                         0x10a95e312 -[__NSArrayI objectAtIndex:] + 38
4   libswiftCore.dylib                     0x10d618149 _CocoaArrayWrapper.subscript.getter + 25
5   OSMFlutterFramework                    0x10b610af0 specialized _ArrayBuffer._getElementSlowPath(_:) + 224
6   OSMFlutterFramework                    0x10b6186c1 MarkerManager.removeMarker(location:) + 913
7   flutter_osm_plugin                     0x10a1d6bbf MapCoreOSMView.deleteMarker(call:) + 543 (osm_map.swift:445)
8   flutter_osm_plugin                     0x10a1c87f4 MapCoreOSMView.onListenMethodChannel(call:result:) + 6308 (osm_map.swift:156)
9   flutter_osm_plugin                     0x10a1c6799 closure #1 in MapCoreOSMView.init(_:viewId:channel:args:defaultPin:) + 57 (osm_map.swift:72)
10  flutter_osm_plugin                     0x10a1c687b thunk for @escaping @callee_guaranteed (@guaranteed FlutterMethodCall, @guaranteed @escaping @callee_guaranteed (@in_guaranteed Any?) -> ()) -> () + 123
11  Flutter                                0x1168069f0 __45-[FlutterMethodChannel setMethodCallHandler:]_block_invoke + 168
12  Flutter                                0x1161c9061 invocation function for block in flutter::PlatformMessageHandlerIos::HandlePlatformMessage(std::_fl::unique_ptr<flutter::PlatformMessage, std::_fl::default_delete<flutter::PlatformMessage>>) + 94
13  libdispatch.dylib                      0x10c0af3ec _dispatch_call_block_and_release + 12
14  libdispatch.dylib                      0x10c0b06d8 _dispatch_client_callout + 8
15  libdispatch.dylib                      0x10c0bf48c _dispatch_main_queue_drain + 1420
16  libdispatch.dylib                      0x10c0beef2 _dispatch_main_queue_callback_4CF + 31
17  CoreFoundation                         0x10a897b34 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
18  CoreFoundation                         0x10a89246f __CFRunLoopRun + 2463
19  CoreFoundation                         0x10a8916ed CFRunLoopRunSpecific + 557
20  GraphicsServices                       0x11c0df08f GSEventRunModal + 137
21  UIKitCore                              0x12d5f46ee -[UIApplication _run] + 972
22  UIKitCore                              0x12d5f916e UIApplicationMain + 123
23  Runner                                 0x100bcf7af main + 63 (AppDelegate.swift:6)
24  dyld_sim                               0x1095023e0 start_sim + 10
25  dyld                                   0x200fb5366 start + 1942

And the strangest part. I modified my code:

try {
      for (var i in [0, 1, 2, 3]) {
        GeoPoint geoPoint = _geoPoints[i];
        debugPrint('removing ${geoPoint.longitude}, ${geoPoint.latitude}');
        await controller.removeMarker(geoPoint);
      }
    } catch (e) {
      debugPrint('Error while removing items from the map: $e');
    }

And it works! But if I add another geopoint, the application crashes again. I thought there was something wrong with these geopoints, but if I remove one at a time (GeoPoint geoPoint = _geoPoints[4]), it works again! I looked at it again and it turns out that with this method I actually remove every other point. So instead of 1st, 2nd, 3rd, 4th geopoints, 1st, 3rd, 5th, 7th are removed (even if I don't give their coordinates!). So probably, when adding the 5th element to the list, the method tries to remove the 9th? geopoint which does not exist? This is really weird:-)

liodali commented 6 months ago

the log is usefull thnx i will work on it and publish a fix this weekend

liodali commented 6 months ago

i will publish new version today,I'm fixing issue in our iOS sdk

liodali commented 6 months ago

the new version 1.0.3 published i can sugges to use removeMarkers or use this way

Future.forEach(_geoPoints, (geoPoint) async {
             await controller.removeMarker(geoPoint);
            await Future.delayed(Duration(milliseconds: 100));
})

also for any error in ios try to run pod update and check that OSMFlutterframework in the version 0.6.4

tmp78 commented 6 months ago

I need more testing, but it looks like it's working now. Thanks!