From 67f48b5429ce491b03fe08633281c6041d256f1f Mon Sep 17 00:00:00 2001 From: Tobrun Date: Fri, 12 Feb 2021 17:01:22 +0100 Subject: [PATCH] [android] migrate to new Android flutter plugin architecture (#488) --- .../mapbox/mapboxgl/GlobalMethodHandler.java | 35 ++- .../com/mapbox/mapboxgl/MapboxMapBuilder.java | 5 +- .../mapbox/mapboxgl/MapboxMapController.java | 181 ++++++-------- .../com/mapbox/mapboxgl/MapboxMapFactory.java | 20 +- .../com/mapbox/mapboxgl/MapboxMapsPlugin.java | 230 +++++++++++++----- .../android/app/src/main/AndroidManifest.xml | 25 +- .../mapboxglexample/EmbeddingV1Activity.java | 16 ++ .../mapbox/mapboxglexample/MainActivity.java | 10 +- 8 files changed, 314 insertions(+), 208 deletions(-) create mode 100644 example/android/app/src/main/java/com/mapbox/mapboxglexample/EmbeddingV1Activity.java diff --git a/android/src/main/java/com/mapbox/mapboxgl/GlobalMethodHandler.java b/android/src/main/java/com/mapbox/mapboxgl/GlobalMethodHandler.java index c5d98ac3e..da3f839a7 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/GlobalMethodHandler.java +++ b/android/src/main/java/com/mapbox/mapboxgl/GlobalMethodHandler.java @@ -5,6 +5,10 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import com.mapbox.mapboxgl.models.OfflineRegionData; +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -15,6 +19,7 @@ import java.io.InputStream; import java.io.OutputStream; +import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry; @@ -23,10 +28,22 @@ class GlobalMethodHandler implements MethodChannel.MethodCallHandler { private static final String TAG = GlobalMethodHandler.class.getSimpleName(); private static final String DATABASE_NAME = "mbgl-offline.db"; private static final int BUFFER_SIZE = 1024 * 2; - private final PluginRegistry.Registrar registrar; - GlobalMethodHandler(PluginRegistry.Registrar registrar) { + @Nullable + private PluginRegistry.Registrar registrar; + @Nullable + private FlutterPlugin.FlutterAssets flutterAssets; + @NonNull + private final Context context; + + GlobalMethodHandler(@NonNull PluginRegistry.Registrar registrar) { this.registrar = registrar; + this.context = registrar.activeContext(); + } + + GlobalMethodHandler(@NonNull Context context, @NonNull FlutterPlugin.FlutterAssets assets) { + this.context = context; + this.flutterAssets = assets; } @Override @@ -58,7 +75,7 @@ public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { } private void installOfflineMapTiles(String tilesDb) { - final File dest = new File(registrar.activeContext().getFilesDir(), DATABASE_NAME); + final File dest = new File(context.getFilesDir(), DATABASE_NAME); try (InputStream input = openTilesDbFile(tilesDb); OutputStream output = new FileOutputStream(dest)) { copy(input, output); @@ -71,7 +88,14 @@ private InputStream openTilesDbFile(String tilesDb) throws IOException { if (tilesDb.startsWith("/")) { // Absolute path. return new FileInputStream(new File(tilesDb)); } else { - final String assetKey = registrar.lookupKeyForAsset(tilesDb); + String assetKey; + if (registrar != null) { + assetKey = registrar.lookupKeyForAsset(tilesDb); + } else if(flutterAssets != null) { + assetKey = flutterAssets.getAssetFilePathByName(tilesDb); + } else { + throw new IllegalStateException(); + } return registrar.activeContext().getAssets().open(assetKey); } } @@ -84,7 +108,7 @@ private String extractAccessToken(MethodCall methodCall, String fallbackValue) { return fallbackValue; } - private static int copy(InputStream input, OutputStream output) throws IOException { + private static void copy(InputStream input, OutputStream output) throws IOException { final byte[] buffer = new byte[BUFFER_SIZE]; final BufferedInputStream in = new BufferedInputStream(input, BUFFER_SIZE); final BufferedOutputStream out = new BufferedOutputStream(output, BUFFER_SIZE); @@ -108,6 +132,5 @@ private static int copy(InputStream input, OutputStream output) throws IOExcepti Log.e(TAG, e.getMessage(), e); } } - return count; } } \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java index f41a00d95..3efa88f4b 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java @@ -13,6 +13,7 @@ import com.mapbox.mapboxsdk.maps.MapboxMapOptions; import com.mapbox.mapboxsdk.maps.Style; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.PluginRegistry; import java.util.concurrent.atomic.AtomicInteger; @@ -30,9 +31,9 @@ class MapboxMapBuilder implements MapboxMapOptionsSink { private String styleString = Style.MAPBOX_STREETS; MapboxMapController build( - int id, Context context, AtomicInteger state, PluginRegistry.Registrar registrar, String accessToken) { + int id, Context context, BinaryMessenger messenger, MapboxMapsPlugin.LifecycleProvider lifecycleProvider, String accessToken) { final MapboxMapController controller = - new MapboxMapController(id, context, state, registrar, options, accessToken, styleString); + new MapboxMapController(id, context, messenger, lifecycleProvider, options, accessToken, styleString); controller.init(); controller.setMyLocationEnabled(myLocationEnabled); controller.setMyLocationTrackingMode(myLocationTrackingMode); diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index b169d531e..b9f6e8ff0 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -5,12 +5,10 @@ package com.mapbox.mapboxgl; import android.Manifest; -import android.app.Activity; -import android.app.Application; +import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.AssetFileDescriptor; -import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.PointF; @@ -19,12 +17,14 @@ import android.os.Build; import android.os.Bundle; import android.util.DisplayMetrics; -import androidx.annotation.NonNull; import android.util.Log; import android.view.Gravity; import android.view.View; import androidx.annotation.NonNull; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; import com.google.gson.Gson; import com.google.gson.JsonArray; @@ -38,63 +38,57 @@ import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdate; - import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.geometry.LatLngQuad; import com.mapbox.mapboxsdk.geometry.VisibleRegion; import com.mapbox.mapboxsdk.location.LocationComponent; import com.mapbox.mapboxsdk.location.LocationComponentOptions; -import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions; import com.mapbox.mapboxsdk.location.OnCameraTrackingChangedListener; import com.mapbox.mapboxsdk.location.modes.CameraMode; import com.mapbox.mapboxsdk.location.modes.RenderMode; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.MapboxMapOptions; -import com.mapbox.mapboxsdk.maps.Projection; -import com.mapbox.mapboxsdk.offline.OfflineManager; import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.Style; +import com.mapbox.mapboxsdk.offline.OfflineManager; import com.mapbox.mapboxsdk.plugins.annotation.Annotation; import com.mapbox.mapboxsdk.plugins.annotation.Circle; import com.mapbox.mapboxsdk.plugins.annotation.CircleManager; import com.mapbox.mapboxsdk.plugins.annotation.Fill; import com.mapbox.mapboxsdk.plugins.annotation.FillManager; +import com.mapbox.mapboxsdk.plugins.annotation.Line; +import com.mapbox.mapboxsdk.plugins.annotation.LineManager; import com.mapbox.mapboxsdk.plugins.annotation.OnAnnotationClickListener; import com.mapbox.mapboxsdk.plugins.annotation.Symbol; import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager; -import com.mapbox.mapboxsdk.plugins.annotation.Line; -import com.mapbox.mapboxsdk.plugins.annotation.LineManager; -import com.mapbox.geojson.Feature; import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions; +import com.mapbox.mapboxsdk.plugins.localization.LocalizationPlugin; import com.mapbox.mapboxsdk.style.expressions.Expression; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.PluginRegistry; -import io.flutter.plugin.platform.PlatformView; +import com.mapbox.mapboxsdk.style.layers.RasterLayer; +import com.mapbox.mapboxsdk.style.sources.ImageSource; import java.io.IOException; import java.io.InputStream; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; - -import static com.mapbox.mapboxgl.MapboxMapsPlugin.CREATED; -import static com.mapbox.mapboxgl.MapboxMapsPlugin.DESTROYED; -import static com.mapbox.mapboxgl.MapboxMapsPlugin.PAUSED; -import static com.mapbox.mapboxgl.MapboxMapsPlugin.RESUMED; -import static com.mapbox.mapboxgl.MapboxMapsPlugin.STARTED; -import static com.mapbox.mapboxgl.MapboxMapsPlugin.STOPPED; - -import com.mapbox.mapboxsdk.plugins.localization.LocalizationPlugin; -import com.mapbox.mapboxsdk.style.layers.RasterLayer; -import com.mapbox.mapboxsdk.style.sources.ImageSource; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.platform.PlatformView; /** * Controller of a single MapboxMaps MapView instance. */ +@SuppressLint("MissingPermission") final class MapboxMapController - implements Application.ActivityLifecycleCallbacks, + implements DefaultLifecycleObserver, MapboxMap.OnCameraIdleListener, MapboxMap.OnCameraMoveListener, MapboxMap.OnCameraMoveStartedListener, @@ -112,10 +106,9 @@ final class MapboxMapController PlatformView { private static final String TAG = "MapboxMapController"; private final int id; - private final AtomicInteger activityState; private final MethodChannel methodChannel; - private final PluginRegistry.Registrar registrar; - private final MapView mapView; + private final MapboxMapsPlugin.LifecycleProvider lifecycleProvider; + private MapView mapView; private MapboxMap mapboxMap; private final Map symbols; private final Map lines; @@ -132,7 +125,6 @@ final class MapboxMapController private boolean disposed = false; private final float density; private MethodChannel.Result mapReadyResult; - private final int registrarActivityHashCode; private final Context context; private final String styleStringInitial; private LocationComponent locationComponent = null; @@ -144,16 +136,14 @@ final class MapboxMapController MapboxMapController( int id, Context context, - AtomicInteger activityState, - PluginRegistry.Registrar registrar, + BinaryMessenger messenger, + MapboxMapsPlugin.LifecycleProvider lifecycleProvider, MapboxMapOptions options, String accessToken, String styleStringInitial) { MapBoxUtils.getMapbox(context, accessToken); this.id = id; this.context = context; - this.activityState = activityState; - this.registrar = registrar; this.styleStringInitial = styleStringInitial; this.mapView = new MapView(context, options); this.symbols = new HashMap<>(); @@ -161,10 +151,9 @@ final class MapboxMapController this.circles = new HashMap<>(); this.fills = new HashMap<>(); this.density = context.getResources().getDisplayMetrics().density; - methodChannel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/mapbox_maps_" + id); + this.lifecycleProvider = lifecycleProvider; + methodChannel = new MethodChannel(messenger, "plugins.flutter.io/mapbox_maps_" + id); methodChannel.setMethodCallHandler(this); - this.registrarActivityHashCode = registrar.activity().hashCode(); } @Override @@ -173,43 +162,7 @@ public View getView() { } void init() { - switch (activityState.get()) { - case STOPPED: - mapView.onCreate(null); - mapView.onStart(); - mapView.onResume(); - mapView.onPause(); - mapView.onStop(); - break; - case PAUSED: - mapView.onCreate(null); - mapView.onStart(); - mapView.onResume(); - mapView.onPause(); - break; - case RESUMED: - mapView.onCreate(null); - mapView.onStart(); - mapView.onResume(); - break; - case STARTED: - mapView.onCreate(null); - mapView.onStart(); - break; - case CREATED: - mapView.onCreate(null); - break; - case DESTROYED: - mapboxMap.removeOnCameraIdleListener(this); - mapboxMap.removeOnCameraMoveStartedListener(this); - mapboxMap.removeOnCameraMoveListener(this); - mapView.onDestroy(); - break; - default: - throw new IllegalArgumentException( - "Cannot interpret " + activityState.get() + " as an activity state"); - } - registrar.activity().getApplication().registerActivityLifecycleCallbacks(this); + lifecycleProvider.getLifecycle().addObserver(this); mapView.getMapAsync(this); } @@ -325,8 +278,7 @@ public void setStyleString(String styleString) { !styleString.startsWith("https://")&& !styleString.startsWith("mapbox://")) { // We are assuming that the style will be loaded from an asset here. - AssetManager assetManager = registrar.context().getAssets(); - String key = registrar.lookupKeyForAsset(styleString); + String key = MapboxMapsPlugin.flutterAssets.getAssetFilePathByName(styleString); mapboxMap.setStyle(new Style.Builder().fromUri("asset://" + key), onStyleLoadedCallback); } else { mapboxMap.setStyle(new Style.Builder().fromUrl(styleString), onStyleLoadedCallback); @@ -387,7 +339,9 @@ private void onUserLocationUpdate(Location location){ userLocation.put("altitude", location.getAltitude()); userLocation.put("bearing", location.getBearing()); userLocation.put("horizontalAccuracy", location.getAccuracy()); - userLocation.put("verticalAccuracy", (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ? location.getVerticalAccuracyMeters() : null); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + userLocation.put("verticalAccuracy", (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ? location.getVerticalAccuracyMeters() : null); + } userLocation.put("timestamp", location.getTime()); final Map arguments = new HashMap<>(1); @@ -997,10 +951,23 @@ public boolean onMapLongClick(@NonNull LatLng point) { @Override public void dispose() { - if (disposed || registrar.activity() == null) { + if (disposed) { return; } disposed = true; + methodChannel.setMethodCallHandler(null); + destroyMapViewIfNecessary(); + Lifecycle lifecycle = lifecycleProvider.getLifecycle(); + if (lifecycle != null) { + lifecycle.removeObserver(this); + } + } + + private void destroyMapViewIfNecessary() { + if (mapView == null) { + return; + } + if (locationComponent != null) { locationComponent.setLocationComponentEnabled(false); } @@ -1017,29 +984,30 @@ public void dispose() { fillManager.onDestroy(); } stopListeningForLocationUpdates(); + mapView.onDestroy(); - registrar.activity().getApplication().unregisterActivityLifecycleCallbacks(this); + mapView = null; } @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { + public void onCreate(@NonNull LifecycleOwner owner) { + if (disposed) { return; } - mapView.onCreate(savedInstanceState); + mapView.onCreate(null); } @Override - public void onActivityStarted(Activity activity) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { + public void onStart(@NonNull LifecycleOwner owner) { + if (disposed) { return; } mapView.onStart(); } @Override - public void onActivityResumed(Activity activity) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { + public void onResume(@NonNull LifecycleOwner owner) { + if (disposed) { return; } mapView.onResume(); @@ -1049,36 +1017,28 @@ public void onActivityResumed(Activity activity) { } @Override - public void onActivityPaused(Activity activity) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { + public void onPause(@NonNull LifecycleOwner owner) { + if (disposed) { return; } - mapView.onPause(); - stopListeningForLocationUpdates(); + mapView.onResume(); } @Override - public void onActivityStopped(Activity activity) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { + public void onStop(@NonNull LifecycleOwner owner) { + if (disposed) { return; } mapView.onStop(); } @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { + public void onDestroy(@NonNull LifecycleOwner owner) { + owner.getLifecycle().removeObserver(this); + if (disposed) { return; } - mapView.onSaveInstanceState(outState); - } - - @Override - public void onActivityDestroyed(Activity activity) { - if (disposed || activity.hashCode() != registrarActivityHashCode) { - return; - } - mapView.onDestroy(); + destroyMapViewIfNecessary(); } // MapboxMapOptionsSink methods @@ -1279,8 +1239,7 @@ private int checkSelfPermission(String permission) { * @return */ private Bitmap getScaledImage(String imageId, float density) { - AssetManager assetManager = registrar.context().getAssets(); - AssetFileDescriptor assetFileDescriptor = null; + AssetFileDescriptor assetFileDescriptor; // Split image path into parts. List imagePathList = Arrays.asList(imageId.split("/")); @@ -1293,7 +1252,7 @@ private Bitmap getScaledImage(String imageId, float density) { String assetPath; if (i == 1) { // If density is 1.0x then simply take the default asset path - assetPath = registrar.lookupKeyForAsset(imageId); + assetPath = MapboxMapsPlugin.flutterAssets.getAssetFilePathByName(imageId); } else { // Build a resolution aware asset path as follows: // // @@ -1306,7 +1265,7 @@ private Bitmap getScaledImage(String imageId, float density) { stringBuilder.append(((float) i) + "x"); stringBuilder.append("/"); stringBuilder.append(imagePathList.get(imagePathList.size()-1)); - assetPath = registrar.lookupKeyForAsset(stringBuilder.toString()); + assetPath = MapboxMapsPlugin.flutterAssets.getAssetFilePathByName(stringBuilder.toString()); } // Build up a list of resolution aware asset paths. assetPathList.add(assetPath); @@ -1317,7 +1276,7 @@ private Bitmap getScaledImage(String imageId, float density) { for (String assetPath : assetPathList) { try { // Read path (throws exception if doesn't exist). - assetFileDescriptor = assetManager.openFd(assetPath); + assetFileDescriptor = mapView.getContext().getAssets().openFd(assetPath); InputStream assetStream = assetFileDescriptor.createInputStream(); bitmap = BitmapFactory.decodeStream(assetStream); assetFileDescriptor.close(); // Close for memory diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapFactory.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapFactory.java index 532e53adc..2cf7b345e 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapFactory.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapFactory.java @@ -1,27 +1,25 @@ package com.mapbox.mapboxgl; -import static io.flutter.plugin.common.PluginRegistry.Registrar; - import android.content.Context; import com.mapbox.mapboxsdk.camera.CameraPosition; +import java.util.Map; + +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.platform.PlatformView; import io.flutter.plugin.platform.PlatformViewFactory; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - public class MapboxMapFactory extends PlatformViewFactory { - private final AtomicInteger mActivityState; - private final Registrar mPluginRegistrar; + private final BinaryMessenger messenger; + private final MapboxMapsPlugin.LifecycleProvider lifecycleProvider; - public MapboxMapFactory(AtomicInteger state, Registrar registrar) { + public MapboxMapFactory(BinaryMessenger messenger, MapboxMapsPlugin.LifecycleProvider lifecycleProvider) { super(StandardMessageCodec.INSTANCE); - mActivityState = state; - mPluginRegistrar = registrar; + this.messenger = messenger; + this.lifecycleProvider = lifecycleProvider; } @Override @@ -34,6 +32,6 @@ public PlatformView create(Context context, int id, Object args) { CameraPosition position = Convert.toCameraPosition(params.get("initialCameraPosition")); builder.setInitialCameraPosition(position); } - return builder.build(id, context, mActivityState, mPluginRegistrar, (String) params.get("accessToken")); + return builder.build(id, context, messenger, lifecycleProvider, (String) params.get("accessToken")); } } diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapsPlugin.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapsPlugin.java index 7fe6c50f1..3598ed12d 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapsPlugin.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapsPlugin.java @@ -8,8 +8,16 @@ import android.app.Application; import android.os.Bundle; -import java.util.concurrent.atomic.AtomicInteger; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LifecycleRegistry; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry.Registrar; @@ -19,87 +27,195 @@ * the map. A Texture drawn using MapboxMap bitmap snapshots can then be shown instead of the * overlay. */ -public class MapboxMapsPlugin implements Application.ActivityLifecycleCallbacks { - static final int CREATED = 1; - static final int STARTED = 2; - static final int RESUMED = 3; - static final int PAUSED = 4; - static final int STOPPED = 5; - static final int DESTROYED = 6; - private final AtomicInteger state = new AtomicInteger(0); - private final int registrarActivityHashCode; +public class MapboxMapsPlugin implements FlutterPlugin, ActivityAware { - public static void registerWith(Registrar registrar) { - if (registrar.activity() == null) { - // When a background flutter view tries to register the plugin, the registrar has no activity. - // We stop the registration process as this plugin is foreground only. - return; - } - final MapboxMapsPlugin plugin = new MapboxMapsPlugin(registrar); - registrar.activity().getApplication().registerActivityLifecycleCallbacks(plugin); - registrar - .platformViewRegistry() - .registerViewFactory( - "plugins.flutter.io/mapbox_gl", new MapboxMapFactory(plugin.state, registrar)); + private static final String VIEW_TYPE = "plugins.flutter.io/mapbox_gl"; - MethodChannel methodChannel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/mapbox_gl"); - methodChannel.setMethodCallHandler(new GlobalMethodHandler(registrar)); + static FlutterAssets flutterAssets; + private Lifecycle lifecycle; + + public MapboxMapsPlugin() { + // no-op } + // New Plugin APIs + @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - if (activity.hashCode() != registrarActivityHashCode) { - return; - } - state.set(CREATED); + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + flutterAssets = binding.getFlutterAssets(); + + MethodChannel methodChannel = new MethodChannel(binding.getBinaryMessenger(), "plugins.flutter.io/mapbox_gl"); + methodChannel.setMethodCallHandler( + new GlobalMethodHandler( + binding.getApplicationContext(), + binding.getFlutterAssets() + ) + ); + + binding + .getPlatformViewRegistry() + .registerViewFactory( + "plugins.flutter.io/mapbox_gl", new MapboxMapFactory(binding.getBinaryMessenger(), new LifecycleProvider() { + @Nullable + @Override + public Lifecycle getLifecycle() { + return lifecycle; + } + })); } @Override - public void onActivityStarted(Activity activity) { - if (activity.hashCode() != registrarActivityHashCode) { - return; - } - state.set(STARTED); + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + // no-op } @Override - public void onActivityResumed(Activity activity) { - if (activity.hashCode() != registrarActivityHashCode) { - return; - } - state.set(RESUMED); + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); } @Override - public void onActivityPaused(Activity activity) { - if (activity.hashCode() != registrarActivityHashCode) { - return; - } - state.set(PAUSED); + public void onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity(); } @Override - public void onActivityStopped(Activity activity) { - if (activity.hashCode() != registrarActivityHashCode) { - return; - } - state.set(STOPPED); + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + onAttachedToActivity(binding); } @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + public void onDetachedFromActivity() { + lifecycle = null; } - @Override - public void onActivityDestroyed(Activity activity) { - if (activity.hashCode() != registrarActivityHashCode) { + // Old Plugin APIs + + public static void registerWith(Registrar registrar) { + final Activity activity = registrar.activity(); + if (activity == null) { + // When a background flutter view tries to register the plugin, the registrar has no activity. + // We stop the registration process as this plugin is foreground only. return; } - state.set(DESTROYED); + if (activity instanceof LifecycleOwner) { + registrar + .platformViewRegistry() + .registerViewFactory( + VIEW_TYPE, + new MapboxMapFactory( + registrar.messenger(), + new LifecycleProvider() { + @Override + public Lifecycle getLifecycle() { + return ((LifecycleOwner) activity).getLifecycle(); + } + })); + } else { + registrar + .platformViewRegistry() + .registerViewFactory( + VIEW_TYPE, + new MapboxMapFactory(registrar.messenger(), new ProxyLifecycleProvider(activity))); + } + + MethodChannel methodChannel = new MethodChannel( + registrar.messenger(), + "plugins.flutter.io/mapbox_gl" + ); + methodChannel.setMethodCallHandler(new GlobalMethodHandler(registrar)); } - private MapboxMapsPlugin(Registrar registrar) { - this.registrarActivityHashCode = registrar.activity().hashCode(); + private static final class ProxyLifecycleProvider + implements Application.ActivityLifecycleCallbacks, LifecycleOwner, LifecycleProvider { + + private final LifecycleRegistry lifecycle = new LifecycleRegistry(this); + private final int registrarActivityHashCode; + + private ProxyLifecycleProvider(Activity activity) { + this.registrarActivityHashCode = activity.hashCode(); + activity.getApplication().registerActivityLifecycleCallbacks(this); + } + + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + if (activity.hashCode() != registrarActivityHashCode) { + return; + } + lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); + } + + @Override + public void onActivityStarted(Activity activity) { + if (activity.hashCode() != registrarActivityHashCode) { + return; + } + lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START); + } + + @Override + public void onActivityResumed(Activity activity) { + if (activity.hashCode() != registrarActivityHashCode) { + return; + } + lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME); + } + + @Override + public void onActivityPaused(Activity activity) { + if (activity.hashCode() != registrarActivityHashCode) { + return; + } + lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE); + } + + @Override + public void onActivityStopped(Activity activity) { + if (activity.hashCode() != registrarActivityHashCode) { + return; + } + lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP); + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} + + @Override + public void onActivityDestroyed(Activity activity) { + if (activity.hashCode() != registrarActivityHashCode) { + return; + } + activity.getApplication().unregisterActivityLifecycleCallbacks(this); + lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY); + } + + @NonNull + @Override + public Lifecycle getLifecycle() { + return lifecycle; + } + } + + interface LifecycleProvider { + @Nullable + Lifecycle getLifecycle(); + } + + /** Provides a static method for extracting lifecycle objects from Flutter plugin bindings. */ + public static class FlutterLifecycleAdapter { + + /** + * Returns the lifecycle object for the activity a plugin is bound to. + * + *

Returns null if the Flutter engine version does not include the lifecycle extraction code. + * (this probably means the Flutter engine version is too old). + */ + @NonNull + public static Lifecycle getActivityLifecycle( + @NonNull ActivityPluginBinding activityPluginBinding) { + HiddenLifecycleReference reference = + (HiddenLifecycleReference) activityPluginBinding.getLifecycle(); + return reference.getLifecycle(); + } } } diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index f0a855080..32dd13477 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,22 +1,14 @@ - - + - @@ -37,6 +25,17 @@ + + + + diff --git a/example/android/app/src/main/java/com/mapbox/mapboxglexample/EmbeddingV1Activity.java b/example/android/app/src/main/java/com/mapbox/mapboxglexample/EmbeddingV1Activity.java new file mode 100644 index 000000000..790679e49 --- /dev/null +++ b/example/android/app/src/main/java/com/mapbox/mapboxglexample/EmbeddingV1Activity.java @@ -0,0 +1,16 @@ +package com.mapbox.mapboxglexample; + +import android.os.Bundle; + +import com.mapbox.mapboxgl.MapboxMapsPlugin; + +import io.flutter.app.FlutterActivity; + +public class EmbeddingV1Activity extends FlutterActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + MapboxMapsPlugin.registerWith(registrarFor("com.mapbox.mapboxgl.MapboxMapsPlugin")); + } +} \ No newline at end of file diff --git a/example/android/app/src/main/java/com/mapbox/mapboxglexample/MainActivity.java b/example/android/app/src/main/java/com/mapbox/mapboxglexample/MainActivity.java index bc3684de7..4e2c0d6ec 100644 --- a/example/android/app/src/main/java/com/mapbox/mapboxglexample/MainActivity.java +++ b/example/android/app/src/main/java/com/mapbox/mapboxglexample/MainActivity.java @@ -1,13 +1,7 @@ package com.mapbox.mapboxglexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import io.flutter.embedding.android.FlutterActivity; public class MainActivity extends FlutterActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); - } + }