Skip to content

Commit 64d470f

Browse files
authored
fix!: more jni related improvements, animateCamera no longer awaits until finished (#90)
- [x] use onMapReady callback - [x] [breaking] animateCamera don't await until the animation has finished
1 parent 9bc9c0a commit 64d470f

File tree

13 files changed

+318
-68
lines changed

13 files changed

+318
-68
lines changed

android/src/main/kotlin/com/github/josxha/maplibre/MapLibreMapController.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ class MapLibreMapController(
141141
this.style = loadedStyle
142142
flutterApi.onStyleLoaded { }
143143
}
144+
flutterApi.onMapReady { }
144145
}
145146

146147
override fun dispose() {
@@ -165,6 +166,7 @@ class MapLibreMapController(
165166
val expression = Expression.Converter.convert(json)
166167
PaintPropertyValue(entry.key, expression)
167168
}
169+
168170
else -> {
169171
PaintPropertyValue(entry.key, value.toArray())
170172
}
@@ -191,6 +193,7 @@ class MapLibreMapController(
191193
val expression = Expression.Converter.convert(json)
192194
LayoutPropertyValue(entry.key, expression)
193195
}
196+
194197
else -> {
195198
LayoutPropertyValue(entry.key, value.toArray())
196199
}

android/src/main/kotlin/com/github/josxha/maplibre/Pigeon.g.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,24 @@ class MapLibreFlutterApi(private val binaryMessenger: BinaryMessenger, private v
776776
}
777777
}
778778
}
779+
/** Callback for when the map is ready and can be used. */
780+
fun onMapReady(callback: (Result<Unit>) -> Unit)
781+
{
782+
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
783+
val channelName = "dev.flutter.pigeon.maplibre.MapLibreFlutterApi.onMapReady$separatedMessageChannelSuffix"
784+
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
785+
channel.send(null) {
786+
if (it is List<*>) {
787+
if (it.size > 1) {
788+
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
789+
} else {
790+
callback(Result.success(Unit))
791+
}
792+
} else {
793+
callback(Result.failure(createConnectionError(channelName)))
794+
}
795+
}
796+
}
779797
/** Callback when the user clicks on the map. */
780798
fun onClick(pointArg: LngLat, callback: (Result<Unit>) -> Unit)
781799
{

example/lib/two_maps_page.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ class _ButtonsWidget extends StatelessWidget {
6666
controller?.moveCamera(
6767
center: Position(172.4714, -42.4862),
6868
zoom: 4,
69+
bearing: 0,
70+
pitch: 0,
6971
);
7072
},
7173
child: const Text('Move to New Zealand'),

ios/Classes/Pigeon.g.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,8 @@ protocol MapLibreFlutterApiProtocol {
743743
func getOptions(completion: @escaping (Result<MapOptions, PigeonError>) -> Void)
744744
/// Callback for when the style has been loaded.
745745
func onStyleLoaded(completion: @escaping (Result<Void, PigeonError>) -> Void)
746+
/// Callback for when the map is ready and can be used.
747+
func onMapReady(completion: @escaping (Result<Void, PigeonError>) -> Void)
746748
/// Callback when the user clicks on the map.
747749
func onClick(point pointArg: LngLat, completion: @escaping (Result<Void, PigeonError>) -> Void)
748750
/// Callback when the map idles.
@@ -812,6 +814,25 @@ class MapLibreFlutterApi: MapLibreFlutterApiProtocol {
812814
}
813815
}
814816
}
817+
/// Callback for when the map is ready and can be used.
818+
func onMapReady(completion: @escaping (Result<Void, PigeonError>) -> Void) {
819+
let channelName: String = "dev.flutter.pigeon.maplibre.MapLibreFlutterApi.onMapReady\(messageChannelSuffix)"
820+
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
821+
channel.sendMessage(nil) { response in
822+
guard let listResponse = response as? [Any?] else {
823+
completion(.failure(createConnectionError(withChannelName: channelName)))
824+
return
825+
}
826+
if listResponse.count > 1 {
827+
let code: String = listResponse[0] as! String
828+
let message: String? = nilOrValue(listResponse[1])
829+
let details: String? = nilOrValue(listResponse[2])
830+
completion(.failure(PigeonError(code: code, message: message, details: details)))
831+
} else {
832+
completion(.success(Void()))
833+
}
834+
}
835+
}
815836
/// Callback when the user clicks on the map.
816837
func onClick(point pointArg: LngLat, completion: @escaping (Result<Void, PigeonError>) -> Void) {
817838
let channelName: String = "dev.flutter.pigeon.maplibre.MapLibreFlutterApi.onClick\(messageChannelSuffix)"

lib/src/map.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,16 @@ class MapLibreMap extends StatefulWidget {
3333
/// for gestures that were not claimed by any other gesture recognizer.
3434
final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
3535

36+
/// Called when the native platform view has been created and the map is
37+
/// ready.
38+
///
3639
/// Please note: you should only add annotations (e.g. symbols or circles)
3740
/// after `onStyleLoadedCallback` has been called.
3841
final MapCreatedCallback? onMapCreated;
3942

4043
/// Called when the map style has been successfully loaded and the annotation
41-
/// managers have been enabled.
44+
/// manager is active.
45+
///
4246
/// Please note: you should only add annotations (e.g. symbols or circles)
4347
/// after this callback has been called.
4448
final VoidCallback? onStyleLoaded;

lib/src/native/pigeon.g.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,9 @@ abstract class MapLibreFlutterApi {
794794
/// Callback for when the style has been loaded.
795795
void onStyleLoaded();
796796

797+
/// Callback for when the map is ready and can be used.
798+
void onMapReady();
799+
797800
/// Callback when the user clicks on the map.
798801
void onClick(LngLat point);
799802

@@ -872,6 +875,29 @@ abstract class MapLibreFlutterApi {
872875
});
873876
}
874877
}
878+
{
879+
final BasicMessageChannel<
880+
Object?> pigeonVar_channel = BasicMessageChannel<
881+
Object?>(
882+
'dev.flutter.pigeon.maplibre.MapLibreFlutterApi.onMapReady$messageChannelSuffix',
883+
pigeonChannelCodec,
884+
binaryMessenger: binaryMessenger);
885+
if (api == null) {
886+
pigeonVar_channel.setMessageHandler(null);
887+
} else {
888+
pigeonVar_channel.setMessageHandler((Object? message) async {
889+
try {
890+
api.onMapReady();
891+
return wrapResponse(empty: true);
892+
} on PlatformException catch (e) {
893+
return wrapResponse(error: e);
894+
} catch (e) {
895+
return wrapResponse(
896+
error: PlatformException(code: 'error', message: e.toString()));
897+
}
898+
});
899+
}
900+
}
875901
{
876902
final BasicMessageChannel<
877903
Object?> pigeonVar_channel = BasicMessageChannel<

lib/src/native/widget_state_jni.dart

Lines changed: 50 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'dart:async';
2-
import 'dart:io';
32
import 'dart:ui';
43

54
import 'package:flutter/material.dart';
@@ -38,42 +37,39 @@ final class MapLibreMapStateJni extends State<MapLibreMap>
3837

3938
@override
4039
Widget build(BuildContext context) {
41-
if (Platform.isAndroid) {
42-
// Texture Layer (or Texture Layer Hybrid Composition)
43-
// Platform Views are rendered into a texture. Flutter draws the
44-
// platform views (via the texture). Flutter content is rendered
45-
// directly into a Surface.
46-
// + good performance for Android Views
47-
// + best performance for Flutter rendering.
48-
// + all transformations work correctly.
49-
// - quick scrolling (e.g. a web view) will be janky
50-
// - SurfaceViews are problematic in this mode and will be moved into a
51-
// virtual display (breaking a11y)
52-
// - Text magnifier will break unless Flutter is rendered into a
53-
// TextureView.
54-
// https://docs.flutter.dev/platform-integration/android/platform-views#texturelayerhybridcompisition
55-
return AndroidView(
56-
viewType: 'plugins.flutter.io/maplibre',
57-
onPlatformViewCreated: _onPlatformViewCreated,
58-
gestureRecognizers: widget.gestureRecognizers,
59-
creationParamsCodec: const StandardMessageCodec(),
60-
);
61-
} else if (Platform.isIOS) {
62-
return UiKitView(
63-
viewType: 'plugins.flutter.io/maplibre',
64-
onPlatformViewCreated: _onPlatformViewCreated,
65-
gestureRecognizers: widget.gestureRecognizers,
66-
creationParamsCodec: const StandardMessageCodec(),
67-
);
68-
}
69-
throw UnsupportedError('[MapLibreMap] Unsupported Platform');
40+
// Texture Layer (or Texture Layer Hybrid Composition)
41+
// Platform Views are rendered into a texture. Flutter draws the
42+
// platform views (via the texture). Flutter content is rendered
43+
// directly into a Surface.
44+
// + good performance for Android Views
45+
// + best performance for Flutter rendering.
46+
// + all transformations work correctly.
47+
// - quick scrolling (e.g. a web view) will be janky
48+
// - SurfaceViews are problematic in this mode and will be moved into a
49+
// virtual display (breaking a11y)
50+
// - Text magnifier will break unless Flutter is rendered into a
51+
// TextureView.
52+
// https://docs.flutter.dev/platform-integration/android/platform-views#texturelayerhybridcompisition
53+
return AndroidView(
54+
viewType: 'plugins.flutter.io/maplibre',
55+
onPlatformViewCreated: _onPlatformViewCreated,
56+
gestureRecognizers: widget.gestureRecognizers,
57+
creationParamsCodec: const StandardMessageCodec(),
58+
);
7059
}
7160

61+
/// This method gets called when the platform view is created. It is not
62+
/// guaranteed that the map is ready.
7263
void _onPlatformViewCreated(int viewId) {
7364
final channelSuffix = viewId.toString();
7465
_hostApi = pigeon.MapLibreHostApi(messageChannelSuffix: channelSuffix);
7566
pigeon.MapLibreFlutterApi.setUp(this, messageChannelSuffix: channelSuffix);
7667
_viewId = viewId;
68+
jni.Logger.setVerbosity(jni.Logger.WARN);
69+
}
70+
71+
@override
72+
void onMapReady() {
7773
widget.onEvent?.call(MapEventMapCreated(mapController: this));
7874
widget.onMapCreated?.call(this);
7975
}
@@ -96,10 +92,6 @@ final class MapLibreMapStateJni extends State<MapLibreMap>
9692

9793
Future<void> _updateOptions(MapLibreMap oldWidget) async {
9894
final jniMap = _jniMapLibreMap;
99-
// We need to check for null to avoid crashes of the app when the map
100-
// options change before the map has been initialized.
101-
if (jniMap.isNull) return;
102-
10395
final oldOptions = oldWidget.options;
10496
final options = _options;
10597
await runOnPlatformThread(() {
@@ -196,6 +188,7 @@ final class MapLibreMapStateJni extends State<MapLibreMap>
196188
double? bearing,
197189
double? pitch,
198190
}) async {
191+
final jniMap = _jniMapLibreMap;
199192
final cameraPositionBuilder = jni.CameraPosition_Builder();
200193
if (center != null) cameraPositionBuilder.target(center.toLatLng());
201194
if (zoom != null) cameraPositionBuilder.zoom(zoom);
@@ -206,24 +199,21 @@ final class MapLibreMapStateJni extends State<MapLibreMap>
206199
cameraPositionBuilder.release();
207200
final cameraUpdate =
208201
jni.CameraUpdateFactory.newCameraPosition(cameraPosition);
209-
210-
final jniMap = _jniMapLibreMap;
211202
await runOnPlatformThread(() {
212203
final completer = Completer<void>();
213-
jniMap.moveCamera$1(
204+
jniMap.moveCamera(cameraUpdate);
205+
/*jniMap.moveCamera$1(
214206
cameraUpdate,
215207
jni.MapLibreMap_CancelableCallback.implement(
216208
jni.$MapLibreMap_CancelableCallback(
217-
onCancel: () => completer.completeError(
218-
Exception('Animation cancelled.'),
219-
),
209+
onCancel: () =>
210+
completer.completeError(Exception('Animation cancelled.')),
220211
onFinish: completer.complete,
221212
onFinish$async: true,
222213
onCancel$async: true,
223214
),
224215
),
225-
);
226-
jniMap.moveCamera(cameraUpdate);
216+
);*/
227217
return completer.future;
228218
});
229219
cameraUpdate.release();
@@ -239,6 +229,7 @@ final class MapLibreMapStateJni extends State<MapLibreMap>
239229
double webSpeed = 1.2,
240230
Duration? webMaxDuration,
241231
}) async {
232+
final jniMap = _jniMapLibreMap;
242233
final cameraPositionBuilder = jni.CameraPosition_Builder();
243234
if (center != null) cameraPositionBuilder.target(center.toLatLng());
244235
if (zoom != null) cameraPositionBuilder.zoom(zoom);
@@ -250,23 +241,22 @@ final class MapLibreMapStateJni extends State<MapLibreMap>
250241
final cameraUpdate =
251242
jni.CameraUpdateFactory.newCameraPosition(cameraPosition);
252243

253-
final jniMapLibreMap = _jniMapLibreMap;
254-
await runOnPlatformThread(() {
244+
await runOnPlatformThread(() async {
255245
final completer = Completer<void>();
256-
jniMapLibreMap.animateCamera$3(
246+
jniMap.animateCamera$2(cameraUpdate, nativeDuration.inMilliseconds);
247+
/*jniMap.animateCamera$3(
257248
cameraUpdate,
258249
nativeDuration.inMilliseconds,
259250
jni.MapLibreMap_CancelableCallback.implement(
260251
jni.$MapLibreMap_CancelableCallback(
261-
onCancel: () => completer.completeError(
262-
Exception('Animation cancelled.'),
263-
),
252+
onCancel: () =>
253+
completer.completeError(Exception('Animation cancelled.')),
264254
onFinish: completer.complete,
265255
onFinish$async: true,
266256
onCancel$async: true,
267257
),
268258
),
269-
);
259+
);*/
270260
return completer.future;
271261
});
272262
cameraUpdate.release();
@@ -285,12 +275,8 @@ final class MapLibreMapStateJni extends State<MapLibreMap>
285275
bool webLinear = false,
286276
EdgeInsets padding = EdgeInsets.zero,
287277
}) async {
288-
final latLngBounds = jni.LatLngBounds.from(
289-
bounds.latitudeNorth,
290-
bounds.longitudeEast,
291-
bounds.latitudeSouth,
292-
bounds.longitudeWest,
293-
);
278+
final jniMap = _jniMapLibreMap;
279+
final latLngBounds = bounds.toLatLngBounds();
294280
final cameraUpdate = jni.CameraUpdateFactory.newLatLngBounds$3(
295281
latLngBounds,
296282
bearing ?? -1.0,
@@ -302,23 +288,22 @@ final class MapLibreMapStateJni extends State<MapLibreMap>
302288
);
303289
latLngBounds.release();
304290

305-
final jniMapLibreMap = _jniMapLibreMap;
306291
await runOnPlatformThread(() {
307292
final completer = Completer<void>();
308-
jniMapLibreMap.animateCamera$3(
293+
jniMap.animateCamera$2(cameraUpdate, nativeDuration.inMilliseconds);
294+
/*jniMap.animateCamera$3(
309295
cameraUpdate,
310296
nativeDuration.inMilliseconds,
311297
jni.MapLibreMap_CancelableCallback.implement(
312298
jni.$MapLibreMap_CancelableCallback(
313-
onCancel: () => completer.completeError(
314-
Exception('Animation cancelled.'),
315-
),
299+
onCancel: () =>
300+
completer.completeError(Exception('Animation cancelled.')),
316301
onFinish: completer.complete,
317302
onCancel$async: true,
318303
onFinish$async: true,
319304
),
320305
),
321-
);
306+
);*/
322307
return completer.future;
323308
});
324309
cameraUpdate.release();
@@ -522,16 +507,14 @@ final class MapLibreMapStateJni extends State<MapLibreMap>
522507
@override
523508
MapCamera getCamera() {
524509
final jniCamera = _jniMapLibreMap.getCameraPosition();
510+
final jniTarget = jniCamera.target;
525511
final camera = MapCamera(
526-
center: Position(
527-
jniCamera.target.getLongitude(),
528-
jniCamera.target.getLatitude(),
529-
),
512+
center: Position(jniTarget.getLongitude(), jniTarget.getLatitude()),
530513
zoom: jniCamera.zoom,
531514
pitch: jniCamera.tilt,
532515
bearing: jniCamera.bearing,
533516
);
534-
jniCamera.target.release();
517+
jniTarget.release();
535518
jniCamera.release();
536519
return camera;
537520
}
@@ -550,7 +533,6 @@ final class MapLibreMapStateJni extends State<MapLibreMap>
550533
final jniBounds = await runOnPlatformThread<jni.LatLngBounds>(() {
551534
final region = projection.getVisibleRegion();
552535
final bounds = region.latLngBounds;
553-
region.latLngBounds.release();
554536
region.release();
555537
return bounds;
556538
});
@@ -561,6 +543,7 @@ final class MapLibreMapStateJni extends State<MapLibreMap>
561543
latitudeNorth: jniBounds.latitudeNorth,
562544
);
563545
jniBounds.release();
546+
jniBounds.release();
564547
return bounds;
565548
}
566549

0 commit comments

Comments
 (0)