From ee82380e456e0cba4130f3ad8e9c8787742a0f79 Mon Sep 17 00:00:00 2001 From: GaelleJoubert <86407953+GaelleJoubert@users.noreply.github.com> Date: Tue, 4 Apr 2023 19:19:08 +0200 Subject: [PATCH] Fix crash android dispose nullpointerdereference (#203) Fix issue #182 --------- Co-authored-by: m0nac0 <58807793+m0nac0@users.noreply.github.com> --- .github/workflows/flutter_ci.yml | 2 +- README.md | 5 + example/android/app/build.gradle | 4 +- example/lib/line.dart | 1 - example/lib/place_fill.dart | 1 - example/lib/place_source.dart | 1 - example/lib/place_symbol.dart | 4 +- example/lib/util.dart | 2 - example/pubspec.lock | 255 ++++++++------ lib/mapbox_gl.dart | 10 +- lib/src/mapbox_map.dart | 13 +- .../lib/maplibre_gl_platform_interface.dart | 3 +- .../lib/src/camera.dart | 2 +- .../lib/src/location.dart | 6 +- .../lib/src/method_channel_mapbox_gl.dart | 45 ++- .../lib/src/ui.dart | 2 +- .../lib/src/view_wrappers.dart | 325 ++++++++++++++++++ maplibre_gl_web/lib/mapbox_gl_web.dart | 1 - pubspec.lock | 83 +++-- 19 files changed, 606 insertions(+), 159 deletions(-) create mode 100644 maplibre_gl_platform_interface/lib/src/view_wrappers.dart diff --git a/.github/workflows/flutter_ci.yml b/.github/workflows/flutter_ci.yml index d13b30fa1..a84605e0b 100644 --- a/.github/workflows/flutter_ci.yml +++ b/.github/workflows/flutter_ci.yml @@ -4,7 +4,7 @@ on: [push, pull_request, workflow_dispatch] env: FLUTTER_CHANNEL: 'stable' - FLUTTER_VERSION: '2.10.5' + FLUTTER_VERSION: '3.7.7' jobs: format: diff --git a/README.md b/README.md index 7cd152a3d..45be70833 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,11 @@ buildTypes { } ``` +## Flutter 3.x.x issues +Since Flutter 3.x.x was introduced, it exposed some race conditions resulting in occasional crashes upon map disposal. The parameter `useDelayedDisposal` was introduced as a workaround for this issue until Flutter and/or Maplibre fix this issue properly. Use with caution. + + + ### iOS app crashes on startup Please include the `NSLocationWhenInUseUsageDescription` as described [here](#location-features) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 252cfbb2f..231b94a07 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -26,7 +26,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 - ndkVersion "20.1.5948944" + ndkVersion "21.1.6352462" lintOptions { disable 'InvalidPackage' @@ -35,7 +35,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.mapbox.mapboxglexample" - minSdkVersion 20 + minSdkVersion 21 targetSdkVersion 31 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/example/lib/line.dart b/example/lib/line.dart index 425120731..8bcb13242 100644 --- a/example/lib/line.dart +++ b/example/lib/line.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart index e53ce91a0..f999403be 100644 --- a/example/lib/place_fill.dart +++ b/example/lib/place_fill.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/example/lib/place_source.dart b/example/lib/place_source.dart index 7b83b9db3..07d8d96fa 100644 --- a/example/lib/place_source.dart +++ b/example/lib/place_source.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/example/lib/place_symbol.dart b/example/lib/place_symbol.dart index 5cb83f230..74b4770cb 100644 --- a/example/lib/place_symbol.dart +++ b/example/lib/place_symbol.dart @@ -5,7 +5,6 @@ import 'dart:async'; // ignore: unnecessary_import import 'dart:core'; import 'dart:math'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -113,7 +112,8 @@ class PlaceSymbolBodyState extends State { return iconImage == 'customFont' ? SymbolOptions( geometry: geometry, - iconImage: 'custom-marker', //'airport-15', + iconImage: 'custom-marker', + //'airport-15', fontNames: ['DIN Offc Pro Bold', 'Arial Unicode MS Regular'], textField: 'Airport', textSize: 12.5, diff --git a/example/lib/util.dart b/example/lib/util.dart index fd7f378d6..c9fea6f12 100644 --- a/example/lib/util.dart +++ b/example/lib/util.dart @@ -1,5 +1,3 @@ -import 'dart:typed_data'; - import 'package:flutter/services.dart'; import 'package:maplibre_gl/mapbox_gl.dart'; diff --git a/example/pubspec.lock b/example/pubspec.lock index 050687fd4..87d59d62b 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,128 +5,146 @@ packages: dependency: transitive description: name: archive - url: "https://pub.dartlang.org" + sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d + url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.6" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + url: "https://pub.dev" source: hosted - version: "2.8.2" + version: "2.10.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: "direct main" description: name: collection - url: "https://pub.dartlang.org" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.17.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" source: hosted version: "3.0.2" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + url: "https://pub.dev" source: hosted version: "1.0.5" device_info_plus: dependency: "direct main" description: name: device_info_plus - url: "https://pub.dartlang.org" + sha256: c2386729379f04cd39ee0d5d4c48d8c8a0e70f7622dac626cbf5e396392602fd + url: "https://pub.dev" source: hosted version: "3.2.4" device_info_plus_linux: dependency: transitive description: name: device_info_plus_linux - url: "https://pub.dartlang.org" + sha256: e4eb5db4704f5534e872148a21cfcd39581022b63df556da6720d88f7c9f91a9 + url: "https://pub.dev" source: hosted version: "2.1.1" device_info_plus_macos: dependency: transitive description: name: device_info_plus_macos - url: "https://pub.dartlang.org" + sha256: "38871fd2ad31871399d8307630c9f4eb5941dd2c643ee221c44d58de95d367a1" + url: "https://pub.dev" source: hosted version: "2.2.3" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - url: "https://pub.dartlang.org" + sha256: b2743934f0efc3e291880d76fb341ea114b7e8417d77ee0f93bd21f5dfd3e8d2 + url: "https://pub.dev" source: hosted - version: "2.3.0+1" + version: "2.6.1" device_info_plus_web: dependency: transitive description: name: device_info_plus_web - url: "https://pub.dartlang.org" + sha256: "38715ad1ef3bee8915dd7bee08a9ac9ab54472a8df425c887062a3046209f663" + url: "https://pub.dev" source: hosted version: "2.1.0" device_info_plus_windows: dependency: transitive description: name: device_info_plus_windows - url: "https://pub.dartlang.org" + sha256: "8fb1403fc94636d6ab48aeebb5f9379f2ca51cde3b337167ec6f39db09234492" + url: "https://pub.dev" source: hosted version: "2.1.1" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.1" ffi: dependency: transitive description: name: ffi - url: "https://pub.dartlang.org" + sha256: "13a6ccf6a459a125b3fcdb6ec73bd5ff90822e071207c663bfd1f70062d51d18" + url: "https://pub.dev" source: hosted version: "1.2.1" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.4" flutter: dependency: "direct main" description: flutter @@ -146,49 +164,56 @@ packages: dependency: "direct main" description: name: http - url: "https://pub.dartlang.org" + sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + url: "https://pub.dev" source: hosted - version: "0.13.4" + version: "0.13.5" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" image: dependency: transitive description: name: image - url: "https://pub.dartlang.org" + sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6" + url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.3.0" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" source: hosted - version: "0.6.3" + version: "0.6.5" location: dependency: "direct main" description: name: location - url: "https://pub.dartlang.org" + sha256: "9051959f6f2ccadd887b28b66e9cbbcc25b6838e37cf9e894c421ccc0ebf80b5" + url: "https://pub.dev" source: hosted version: "4.4.0" location_platform_interface: dependency: transitive description: name: location_platform_interface - url: "https://pub.dartlang.org" + sha256: "62eeaf1658e92e4459b727f55a3c328eccbac8ba043fa6d262ac5286ad48384c" + url: "https://pub.dev" source: hosted version: "2.3.0" location_web: dependency: transitive description: name: location_web - url: "https://pub.dartlang.org" + sha256: "6c08c408a040534c0269c4ff9fe17eebb5a36dea16512fbaf116b9c8bc21545b" + url: "https://pub.dev" source: hosted version: "3.1.1" maplibre_gl: @@ -225,105 +250,120 @@ packages: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + url: "https://pub.dev" source: hosted - version: "0.12.11" + version: "0.12.13" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted - version: "0.1.3" + version: "0.2.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.8.2" path_provider: dependency: "direct main" description: name: path_provider - url: "https://pub.dartlang.org" + sha256: c7edf82217d4b2952b2129a61d3ad60f1075b9299e629e149a8d2e39c2e6aad4 + url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.0.14" path_provider_android: dependency: transitive description: name: path_provider_android - url: "https://pub.dartlang.org" + sha256: "019f18c9c10ae370b08dce1f3e3b73bc9f58e7f087bb5e921f06529438ac0ae7" + url: "https://pub.dev" source: hosted - version: "2.0.14" - path_provider_ios: + version: "2.0.24" + path_provider_foundation: dependency: transitive description: - name: path_provider_ios - url: "https://pub.dartlang.org" + name: path_provider_foundation + sha256: "12eee51abdf4d34c590f043f45073adbb45514a108bd9db4491547a2fd891059" + url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.2.0" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.7" - path_provider_macos: - dependency: transitive - description: - name: path_provider_macos - url: "https://pub.dartlang.org" + sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" + url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.1.10" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.0.6" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + sha256: a34ecd7fb548f8e57321fd8e50d865d266941b54e6c3b7758cf8f37c24116905 + url: "https://pub.dev" source: hosted version: "2.0.7" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "5.1.0" platform: dependency: "direct main" description: name: platform - url: "https://pub.dartlang.org" + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" source: hosted version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: c3120a968135aead39699267f4c74bc9a08e4e909e86bc1b0af5bfd78691123c + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "3.7.2" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" source: hosted version: "4.2.4" sky_engine: @@ -335,79 +375,90 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" source: hosted - version: "1.8.1" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + url: "https://pub.dev" source: hosted - version: "0.4.8" + version: "0.4.16" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.1" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" win32: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + sha256: c0e3a4f7be7dae51d8f152230b86627e3397c1ba8c3fa58e63d44a9f3edc9cef + url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.6.1" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + url: "https://pub.dev" source: hosted - version: "0.2.0+1" + version: "1.0.0" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + url: "https://pub.dev" source: hosted - version: "5.3.1" + version: "6.2.2" sdks: - dart: ">=2.15.0 <3.0.0" - flutter: ">=2.8.1" + dart: ">=2.18.0 <3.0.0" + flutter: ">=3.0.0" diff --git a/lib/mapbox_gl.dart b/lib/mapbox_gl.dart index 050d22d22..b62285552 100644 --- a/lib/mapbox_gl.dart +++ b/lib/mapbox_gl.dart @@ -7,7 +7,6 @@ library maplibre_gl; import 'dart:async'; import 'dart:convert'; import 'dart:math'; -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -50,12 +49,21 @@ export 'package:maplibre_gl_platform_interface/maplibre_gl_platform_interface.da ImageSourceProperties; part 'src/controller.dart'; + part 'src/mapbox_map.dart'; + part 'src/global.dart'; + part 'src/offline_region.dart'; + part 'src/download_region_status.dart'; + part 'src/layer_expressions.dart'; + part 'src/layer_properties.dart'; + part 'src/color_tools.dart'; + part 'src/annotation_manager.dart'; + part 'src/util.dart'; diff --git a/lib/src/mapbox_map.dart b/lib/src/mapbox_map.dart index 66d656452..54f40e875 100644 --- a/lib/src/mapbox_map.dart +++ b/lib/src/mapbox_map.dart @@ -53,6 +53,8 @@ class MaplibreMap extends StatefulWidget { AnnotationType.line, AnnotationType.circle, ], + this.useDelayedDisposal, + this.useHybridCompositionOverride, }) : assert(annotationOrder.length <= 4), assert(annotationConsumeTapEvents.length > 0), super(key: key); @@ -211,11 +213,18 @@ class MaplibreMap extends StatefulWidget { /// * All fade/transition animations have completed final OnMapIdleCallback? onMapIdle; + /// Use delayed disposal of Android View Controller to avoid flutter 3.x.x crashes + final bool? useDelayedDisposal; + + /// Override hybrid mode per map instance + final bool? useHybridCompositionOverride; + /// Set `MapboxMap.useHybridComposition` to `false` in order use Virtual-Display /// (better for Android 9 and below but may result in errors on Android 12) /// or leave it `true` (default) to use Hybrid composition (Slower on Android 9 and below). static bool get useHybridComposition => MethodChannelMaplibreGl.useHybridComposition; + static set useHybridComposition(bool useHybridComposition) => MethodChannelMaplibreGl.useHybridComposition = useHybridComposition; @@ -240,7 +249,9 @@ class _MaplibreMapState extends State { 'initialCameraPosition': widget.initialCameraPosition.toMap(), 'options': _MapboxMapOptions.fromWidget(widget).toMap(), //'onAttributionClickOverride': widget.onAttributionClick != null, - 'dragEnabled': widget.dragEnabled + 'dragEnabled': widget.dragEnabled, + 'useDelayedDisposal': widget.useDelayedDisposal, + 'useHybridCompositionOverride': widget.useHybridCompositionOverride, }; return _mapboxGlPlatform.buildView( creationParams, onPlatformViewCreated, widget.gestureRecognizers); diff --git a/maplibre_gl_platform_interface/lib/maplibre_gl_platform_interface.dart b/maplibre_gl_platform_interface/lib/maplibre_gl_platform_interface.dart index b8ddf9a01..7b96000ea 100644 --- a/maplibre_gl_platform_interface/lib/maplibre_gl_platform_interface.dart +++ b/maplibre_gl_platform_interface/lib/maplibre_gl_platform_interface.dart @@ -3,13 +3,12 @@ library maplibre_gl_platform_interface; import 'dart:async'; import 'dart:convert'; import 'dart:math'; -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; - +part 'src/view_wrappers.dart'; part 'src/annotation.dart'; part 'src/callbacks.dart'; part 'src/camera.dart'; diff --git a/maplibre_gl_platform_interface/lib/src/camera.dart b/maplibre_gl_platform_interface/lib/src/camera.dart index 2b54798c4..0a2efd3dc 100644 --- a/maplibre_gl_platform_interface/lib/src/camera.dart +++ b/maplibre_gl_platform_interface/lib/src/camera.dart @@ -80,7 +80,7 @@ class CameraPosition { } @override - int get hashCode => hashValues(bearing, target, tilt, zoom); + int get hashCode => Object.hash(bearing, target, tilt, zoom); @override String toString() => diff --git a/maplibre_gl_platform_interface/lib/src/location.dart b/maplibre_gl_platform_interface/lib/src/location.dart index a1b5567f0..0e75306cd 100644 --- a/maplibre_gl_platform_interface/lib/src/location.dart +++ b/maplibre_gl_platform_interface/lib/src/location.dart @@ -53,7 +53,7 @@ class LatLng { } @override - int get hashCode => hashValues(latitude, longitude); + int get hashCode => Object.hash(latitude, longitude); } /// A latitude/longitude aligned rectangle. @@ -106,7 +106,7 @@ class LatLngBounds { } @override - int get hashCode => hashValues(southwest, northeast); + int get hashCode => Object.hash(southwest, northeast); } /// A geographical area representing a non-aligned quadrilateral @@ -164,7 +164,7 @@ class LatLngQuad { } @override - int get hashCode => hashValues(topLeft, topRight, bottomRight, bottomLeft); + int get hashCode => Object.hash(topLeft, topRight, bottomRight, bottomLeft); } /// User's observed location diff --git a/maplibre_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/maplibre_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart index 1fef7d5cc..7c5bf528e 100644 --- a/maplibre_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/maplibre_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -138,7 +138,12 @@ class MethodChannelMaplibreGl extends MapLibreGlPlatform { OnPlatformViewCreatedCallback onPlatformViewCreated, Set>? gestureRecognizers) { if (defaultTargetPlatform == TargetPlatform.android) { - if (useHybridComposition) { + final useDelayedDisposalParam = + (creationParams['useDelayedDisposal'] ?? false) as bool; + final useHybridCompositionParam = + (creationParams['useHybridCompositionOverride'] ?? + useHybridComposition) as bool; + if (useHybridCompositionParam) { return PlatformViewLink( viewType: 'plugins.flutter.io/mapbox_gl', surfaceFactory: ( @@ -153,15 +158,26 @@ class MethodChannelMaplibreGl extends MapLibreGlPlatform { ); }, onCreatePlatformView: (PlatformViewCreationParams params) { - final SurfaceAndroidViewController controller = - PlatformViewsService.initSurfaceAndroidView( - id: params.id, - viewType: 'plugins.flutter.io/mapbox_gl', - layoutDirection: TextDirection.ltr, - creationParams: creationParams, - creationParamsCodec: const StandardMessageCodec(), - onFocus: () => params.onFocusChanged(true), - ); + late AndroidViewController controller; + if (useDelayedDisposalParam) { + controller = WrappedPlatformViewsService.initAndroidView( + id: params.id, + viewType: 'plugins.flutter.io/mapbox_gl', + layoutDirection: TextDirection.ltr, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + onFocus: () => params.onFocusChanged(true), + ); + } else { + controller = PlatformViewsService.initAndroidView( + id: params.id, + viewType: 'plugins.flutter.io/mapbox_gl', + layoutDirection: TextDirection.ltr, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + onFocus: () => params.onFocusChanged(true), + ); + } controller.addOnPlatformViewCreatedListener( params.onPlatformViewCreated, ); @@ -174,6 +190,15 @@ class MethodChannelMaplibreGl extends MapLibreGlPlatform { }, ); } else { + if (useDelayedDisposalParam) { + return AndroidViewWithWrappedController( + viewType: 'plugins.flutter.io/mapbox_gl', + onPlatformViewCreated: onPlatformViewCreated, + gestureRecognizers: gestureRecognizers, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ); + } return AndroidView( viewType: 'plugins.flutter.io/mapbox_gl', onPlatformViewCreated: onPlatformViewCreated, diff --git a/maplibre_gl_platform_interface/lib/src/ui.dart b/maplibre_gl_platform_interface/lib/src/ui.dart index aa5749552..d24723f42 100644 --- a/maplibre_gl_platform_interface/lib/src/ui.dart +++ b/maplibre_gl_platform_interface/lib/src/ui.dart @@ -105,7 +105,7 @@ class MinMaxZoomPreference { } @override - int get hashCode => hashValues(minZoom, maxZoom); + int get hashCode => Object.hash(minZoom, maxZoom); @override String toString() { diff --git a/maplibre_gl_platform_interface/lib/src/view_wrappers.dart b/maplibre_gl_platform_interface/lib/src/view_wrappers.dart new file mode 100644 index 000000000..b81167e3a --- /dev/null +++ b/maplibre_gl_platform_interface/lib/src/view_wrappers.dart @@ -0,0 +1,325 @@ +part of maplibre_gl_platform_interface; + +/// This file wrapps AndroidViewController classes in order to delay disposal process. +/// It is an workaround for flutter 3, where resourses get disposed quicker than before, while Mapbox behaves badly +/// and tries to access those resources after they had been disposed, resulting in a native crash. +class WrappedPlatformViewsService { + static AndroidViewController initAndroidView({ + required int id, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + VoidCallback? onFocus, + }) { + final view = PlatformViewsService.initAndroidView( + id: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: creationParams, + creationParamsCodec: creationParamsCodec, + onFocus: onFocus, + ); + return view; + } +} + +class TextureAndroidViewControllerWrapper + implements TextureAndroidViewController { + TextureAndroidViewControllerWrapper(this._controller); + + final TextureAndroidViewController _controller; + + // @override + PointTransformer get pointTransformer => _controller.pointTransformer; + + set pointTransformer(PointTransformer transformer) => + _controller.pointTransformer = transformer; + + // @override + void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) => + _controller.addOnPlatformViewCreatedListener(listener); + + /// Beginning with flutter 3, [_controller.awaitingCreation] should be called. + /// + /// A false value is returned in order to consolidate usage with flutter 2. + /// A false return value does not necessarily indicate that the Future + /// returned by create has completed, only that creation has been started. + // @override + bool awaitingCreation = true; + + // @override + Future clearFocus() => _controller.clearFocus(); + + /// Beginning with flutter 3, [_controller.create] with [size] should be called. + /// + /// size is the view's initial size in logical pixel. size can be omitted + /// if the concrete implementation doesn't require an initial size to create + /// the platform view. + Future create({Offset? position, Size? size}) async { + await _controller.create(); + awaitingCreation = false; + if (size != null) { + await _controller.setSize(size); + if (position != null) { + await _controller.setOffset(position); + } + } + } + + /// Beginning with flutter 3, [_controller.createdCallbacks] should be called. + /// + /// This is for testing purposes only and is not relevant for production code. + // @override + // ignore: invalid_use_of_visible_for_testing_member + List get createdCallbacks => []; + + // @override + Future dispatchPointerEvent(PointerEvent event) => + _controller.dispatchPointerEvent(event); + + /// Beginning with flutter 3, disposal is called to soon, resulting in crashes. + /// + /// This is a workaround for users to be able to use this plugin with flutter 3. + // ignore: invalid_use_of_visible_for_testing_member + Future dispose() { + //? instead of this + // _controller.dispose(); + //? we do this + unawaited(Future.delayed(Duration(seconds: 5), _controller.dispose)); + return Future(() {}); + } + + // @override + bool get isCreated => _controller.isCreated; + + // @override + void removeOnPlatformViewCreatedListener( + PlatformViewCreatedCallback listener) => + _controller.removeOnPlatformViewCreatedListener(listener); + + // @override + Future sendMotionEvent(AndroidMotionEvent event) => + _controller.sendMotionEvent(event); + + // @override + Future setLayoutDirection(TextDirection layoutDirection) => + _controller.setLayoutDirection(layoutDirection); + + /// Beginning with flutter 3, [_controller.setOffset(off)] should be called. + /// + /// off is the view's new offset in logical pixel. + /// On Android, this allows the Android native view to draw the a11y highlights + /// in the same location on the screen as the platform view widget in the Flutter framework.L + // @override + Future setOffset(Offset off) => Future(() => {}); + + // @override + Future setSize(Size size) async { + await _controller.setSize(size); + return size; + } + + // @override + int? get textureId => _controller.textureId; + + // @override + int get viewId => _controller.viewId; + + //@override + bool get requiresViewComposition => _controller.requiresViewComposition; +} + +class AndroidViewWithWrappedController extends StatefulWidget { + const AndroidViewWithWrappedController({ + Key? key, + required this.viewType, + this.onPlatformViewCreated, + this.hitTestBehavior = PlatformViewHitTestBehavior.opaque, + this.layoutDirection, + this.gestureRecognizers, + this.creationParams, + this.creationParamsCodec, + this.clipBehavior = Clip.hardEdge, + }) : assert(creationParams == null || creationParamsCodec != null), + super(key: key); + + final String viewType; + final PlatformViewCreatedCallback? onPlatformViewCreated; + final PlatformViewHitTestBehavior hitTestBehavior; + final TextDirection? layoutDirection; + final Set>? gestureRecognizers; + final dynamic creationParams; + final MessageCodec? creationParamsCodec; + final Clip clipBehavior; + + @override + State createState() => + _AndroidViewWithWrappedControllerState(); +} + +class _AndroidViewWithWrappedControllerState + extends State { + int? _id; + late AndroidViewController _controller; + TextDirection? _layoutDirection; + bool _initialized = false; + FocusNode? _focusNode; + + static final Set> _emptyRecognizersSet = + >{}; + + @override + Widget build(BuildContext context) { + return Focus( + focusNode: _focusNode, + onFocusChange: _onFocusChange, + child: _CopyPastedAndroidPlatformView( + controller: _controller, + hitTestBehavior: widget.hitTestBehavior, + gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet, + clipBehavior: widget.clipBehavior, + ), + ); + } + + void _initializeOnce() { + if (_initialized) { + return; + } + _initialized = true; + _createNewAndroidView(); + _focusNode = FocusNode(debugLabel: 'AndroidView(id: $_id)'); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final TextDirection newLayoutDirection = _findLayoutDirection(); + final bool didChangeLayoutDirection = + _layoutDirection != newLayoutDirection; + _layoutDirection = newLayoutDirection; + + _initializeOnce(); + if (didChangeLayoutDirection) { + // The native view will update asynchronously, in the meantime we don't want + // to block the framework. (so this is intentionally not awaiting). + _controller.setLayoutDirection(_layoutDirection!); + } + } + + @override + void didUpdateWidget(AndroidViewWithWrappedController oldWidget) { + super.didUpdateWidget(oldWidget); + + final TextDirection newLayoutDirection = _findLayoutDirection(); + final bool didChangeLayoutDirection = + _layoutDirection != newLayoutDirection; + _layoutDirection = newLayoutDirection; + + if (widget.viewType != oldWidget.viewType) { + _controller.dispose(); + _createNewAndroidView(); + return; + } + + if (didChangeLayoutDirection) { + _controller.setLayoutDirection(_layoutDirection!); + } + } + + TextDirection _findLayoutDirection() { + assert( + widget.layoutDirection != null || debugCheckHasDirectionality(context)); + return widget.layoutDirection ?? Directionality.of(context); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void _createNewAndroidView() { + _id = platformViewsRegistry.getNextPlatformViewId(); + _controller = WrappedPlatformViewsService.initAndroidView( + id: _id!, + viewType: widget.viewType, + layoutDirection: _layoutDirection!, + creationParams: widget.creationParams, + creationParamsCodec: widget.creationParamsCodec, + onFocus: () { + _focusNode!.requestFocus(); + }, + ); + if (widget.onPlatformViewCreated != null) { + _controller + .addOnPlatformViewCreatedListener(widget.onPlatformViewCreated!); + } + } + + void _onFocusChange(bool isFocused) { + if (!_controller.isCreated) { + return; + } + if (!isFocused) { + _controller.clearFocus().catchError((dynamic e) { + if (e is MissingPluginException) { + // We land the framework part of Android platform views keyboard + // support before the engine part. There will be a commit range where + // clearFocus isn't implemented in the engine. When that happens we + // just swallow the error here. Once the engine part is rolled to the + // framework I'll remove this. + // TODO(amirh): remove this once the engine's clearFocus is rolled. + return; + } + }); + return; + } + SystemChannels.textInput.invokeMethod( + 'TextInput.setPlatformViewClient', + {'platformViewId': _id}, + ).catchError((dynamic e) { + if (e is MissingPluginException) { + // We land the framework part of Android platform views keyboard + // support before the engine part. There will be a commit range where + // setPlatformViewClient isn't implemented in the engine. When that + // happens we just swallow the error here. Once the engine part is + // rolled to the framework I'll remove this. + // TODO(amirh): remove this once the engine's clearFocus is rolled. + return; + } + }); + } +} + +class _CopyPastedAndroidPlatformView extends LeafRenderObjectWidget { + const _CopyPastedAndroidPlatformView({ + required this.controller, + required this.hitTestBehavior, + required this.gestureRecognizers, + this.clipBehavior = Clip.hardEdge, + }); + + final AndroidViewController controller; + final PlatformViewHitTestBehavior hitTestBehavior; + final Set> gestureRecognizers; + final Clip clipBehavior; + + @override + RenderObject createRenderObject(BuildContext context) => RenderAndroidView( + viewController: controller, + hitTestBehavior: hitTestBehavior, + gestureRecognizers: gestureRecognizers, + clipBehavior: clipBehavior, + ); + + @override + void updateRenderObject( + BuildContext context, RenderAndroidView renderObject) { + //renderObject.controller = controller; + renderObject.hitTestBehavior = hitTestBehavior; + renderObject.updateGestureRecognizers(gestureRecognizers); + renderObject.clipBehavior = clipBehavior; + } +} diff --git a/maplibre_gl_web/lib/mapbox_gl_web.dart b/maplibre_gl_web/lib/mapbox_gl_web.dart index 91ff5d97b..2765552dd 100644 --- a/maplibre_gl_web/lib/mapbox_gl_web.dart +++ b/maplibre_gl_web/lib/mapbox_gl_web.dart @@ -8,7 +8,6 @@ import 'dart:html'; import 'dart:js'; import 'dart:js_util'; import 'dart:math'; -import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/services.dart'; diff --git a/pubspec.lock b/pubspec.lock index 30100e68d..670a78dac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,30 +5,42 @@ packages: dependency: transitive description: name: archive - url: "https://pub.dartlang.org" + sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d + url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.3.6" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.17.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" flutter: dependency: "direct main" description: flutter @@ -43,16 +55,18 @@ packages: dependency: transitive description: name: image - url: "https://pub.dartlang.org" + sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6" + url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.3.0" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" source: hosted - version: "0.6.3" + version: "0.6.5" maplibre_gl_dart: dependency: transitive description: @@ -80,30 +94,42 @@ packages: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted - version: "0.1.3" + version: "0.2.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.8.3" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: c3120a968135aead39699267f4c74bc9a08e4e909e86bc1b0af5bfd78691123c + url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "3.7.2" sky_engine: dependency: transitive description: flutter @@ -113,23 +139,26 @@ packages: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.1" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "6.2.2" sdks: - dart: ">=2.14.0 <3.0.0" + dart: ">=2.18.0 <3.0.0" flutter: ">=2.0.0"