Skip to content

Update to Flutter Plugin V2 embedding; deprecate old methods for back… #87

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 1.6.0 - 2025-05-06
- Updated to use the modern FlutterPlugin.FlutterPluginBinding pattern
- Deprecated Registrar, PluginRegistrantCallback, and ShimPluginRegistry usage
- Added backward compatibility for older Flutter versions
- Improved handling of plugin registration in background execution

## 1.5.0 - 2025-01-29
- Updated gradle dependencies

Expand Down
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ Or add the info to the Info.plist

### Update the AppDelegate

Make sure you call the `setPluginRegistrantCallback` so other plugins can be accessed in the background.

```
import UIKit
import Flutter
Expand All @@ -65,6 +63,8 @@ import background_location_tracker
override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
GeneratedPluginRegistrant.register(with: self)

// Register plugins for background execution - this method is deprecated
// but kept for backward compatibility
BackgroundLocationTrackerPlugin.setPluginRegistrantCallback { registry in
GeneratedPluginRegistrant.register(with: registry)
}
Expand Down Expand Up @@ -124,7 +124,21 @@ Future stopLocationTracking() async {

```
This is mostly caused by a misconfiguration of the plugin:
Android Pre v2 embedding: make sure the plugin registrant callback is set
Android v2 embedding: Log a new github issues. This
iOS: make sure the plugin registrant callback is set
- Android: This plugin now uses Flutter's Plugin V2 embedding pattern (FlutterPluginBinding) so you don't need to set a manual plugin registrant callback.
- iOS: For compatibility with older Flutter versions, the setPluginRegistrantCallback is still available but will be removed in a future version.

If you're using Flutter 1.12 or later, the plugin should work out of the box with the V2 embedding.
```

## Migration Guide

### Upgrading from older versions

This plugin has been updated to use the modern Flutter Plugin V2 embedding pattern with FlutterPluginBinding. If you're using Flutter 1.12 or later, you don't need to make any changes to your app's code.

For backward compatibility, the older methods are still available but marked as deprecated:

- `BackgroundLocationTrackerPlugin.setPluginRegistrantCallback` on Android
- `BackgroundLocationTrackerPlugin.setPluginRegistrantCallback` on iOS

These methods will be removed in a future version of the plugin.
2 changes: 1 addition & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ linter:
- one_member_abstracts
- only_throw_errors
- overridden_fields
- package_api_docs
# - package_api_docs
- package_names
- package_prefixed_library_names
- parameter_assignments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,29 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
import io.flutter.embedding.engine.FlutterEngine

class BackgroundLocationTrackerPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
private var lifecycle: Lifecycle? = null
private var methodCallHelper: MethodCallHelper? = null

override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
registerBackgroundLocationManager(binding.binaryMessenger, binding.applicationContext)
private var channel: MethodChannel? = null
private var applicationContext: Context? = null

override fun onAttachedToEngine(binding: FlutterPluginBinding) {
applicationContext = binding.applicationContext
channel = MethodChannel(binding.binaryMessenger, FOREGROUND_CHANNEL_NAME)
channel?.setMethodCallHandler(this)

if (methodCallHelper == null) {
methodCallHelper = MethodCallHelper.getInstance(binding.applicationContext)
}
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
channel?.setMethodCallHandler(null)
channel = null
applicationContext = null
}

override fun onMethodCall(call: MethodCall, result: Result) {
Expand All @@ -38,10 +50,7 @@ class BackgroundLocationTrackerPlugin : FlutterPlugin, MethodCallHandler, Activi

override fun onAttachedToActivity(binding: ActivityPluginBinding) {
lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding)
if (methodCallHelper == null) {
ActivityCounter.attach(binding.activity)
methodCallHelper = MethodCallHelper.getInstance(binding.activity.applicationContext)
}
ActivityCounter.attach(binding.activity)
methodCallHelper?.let {
lifecycle?.removeObserver(it)
lifecycle?.addObserver(it)
Expand All @@ -50,67 +59,42 @@ class BackgroundLocationTrackerPlugin : FlutterPlugin, MethodCallHandler, Activi

override fun onDetachedFromActivity() {}

override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
onAttachedToActivity(binding)
}

override fun onDetachedFromActivityForConfigChanges() {}
override fun onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity()
}

companion object {
private const val TAG = "FBLTPlugin"
private const val FOREGROUND_CHANNEL_NAME = "com.icapps.background_location_tracker/foreground_channel"

var pluginRegistryCallback: PluginRegistry.PluginRegistrantCallback? = null
// New static properties for background execution
private var flutterEngine: FlutterEngine? = null

@JvmStatic
private fun registerBackgroundLocationManager(messenger: BinaryMessenger, ctx: Context) {
val channel = MethodChannel(messenger, FOREGROUND_CHANNEL_NAME)
channel.setMethodCallHandler(BackgroundLocationTrackerPlugin().apply {
if (methodCallHelper == null) {
methodCallHelper = MethodCallHelper.getInstance(ctx)
}
methodCallHelper?.let {
lifecycle?.removeObserver(it)
lifecycle?.addObserver(it)
}
})
}
// For compatibility with older plugins
@Deprecated("Use FlutterEngine's plugin registry instead")
private var pluginRegistrantCallback: ((FlutterEngine) -> Unit)? = null

@JvmStatic
fun registerWith(registrar: PluginRegistry.Registrar) {
val activity = registrar.activity()
if (activity == null) {
Logger.debug(TAG, "Activity should not be null while registering this plugin")
return
}

val lifecycle: Lifecycle = if (activity is LifecycleOwner) {
(activity as LifecycleOwner).lifecycle
} else {
Logger.debug(TAG, "Your activity has not implemented a lifecycle owner. We will create one for you.")
@Suppress("DEPRECATION")
ProxyLifecycleProvider(activity).lifecycle
}

ActivityCounter.attach(activity)
val channel = MethodChannel(registrar.messenger(), FOREGROUND_CHANNEL_NAME)
channel.setMethodCallHandler(BackgroundLocationTrackerPlugin().apply {
if (methodCallHelper == null) {
methodCallHelper = MethodCallHelper.getInstance(registrar.activeContext())
}
methodCallHelper?.let {
lifecycle.removeObserver(it)
lifecycle.addObserver(it)
}
})
@Deprecated("Use the Android embedding v2 instead")
fun setPluginRegistrantCallback(callback: ((FlutterEngine) -> Unit)) {
pluginRegistrantCallback = callback
}

@Deprecated(message = "Use the Android v2 embedding method.")
// Method to get or create the Flutter engine for background execution
@JvmStatic
fun setPluginRegistrantCallback(pluginRegistryCallback: PluginRegistry.PluginRegistrantCallback) {
BackgroundLocationTrackerPlugin.pluginRegistryCallback = pluginRegistryCallback
fun getFlutterEngine(context: Context): FlutterEngine {
return flutterEngine ?: FlutterEngine(context).also {
flutterEngine = it
pluginRegistrantCallback?.invoke(it)
}
}
}

@Deprecated(message = "Use the Android v2 embedding method.")
@Deprecated("Use the Android embedding v2 instead")
private class ProxyLifecycleProvider internal constructor(activity: Activity) : Application.ActivityLifecycleCallbacks, LifecycleOwner {
override val lifecycle = LifecycleRegistry(this)
private val registrarActivityHashCode: Int = activity.hashCode()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import com.icapps.background_location_tracker.utils.SharedPrefsUtil
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.embedding.engine.loader.FlutterLoader
import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.view.FlutterCallbackInformation
Expand All @@ -24,10 +23,7 @@ internal object FlutterBackgroundManager {
private fun getInitializedFlutterEngine(ctx: Context): FlutterEngine {
Logger.debug("BackgroundManager", "Creating new engine")

val engine = FlutterEngine(ctx)
//Backwards compatibility with v1. We register all the user's plugins.
BackgroundLocationTrackerPlugin.pluginRegistryCallback?.registerWith(ShimPluginRegistry(engine))
return engine
return BackgroundLocationTrackerPlugin.getFlutterEngine(ctx)
}

fun sendLocation(ctx: Context, location: Location) {
Expand Down
2 changes: 1 addition & 1 deletion example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="example"
android:name="${applicationName}"
android:name=".Application"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example.example

import io.flutter.app.FlutterApplication
import com.icapps.background_location_tracker.BackgroundLocationTrackerPlugin
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugins.GeneratedPluginRegistrant

// This Application class shows both approaches:
// 1. The deprecated approach using PluginRegistrantCallback (for backward compatibility)
// 2. Comments showing that this is not needed with the modern FlutterPluginBinding approach
class Application : FlutterApplication() {
override fun onCreate() {
super.onCreate()

// Note: This is the deprecated approach and is only kept for backward compatibility
// With modern Flutter plugins using the v2 embedding (FlutterPluginBinding),
// this callback registration is not necessary
BackgroundLocationTrackerPlugin.setPluginRegistrantCallback { engine ->
// In a modern plugin using FlutterPluginBinding, plugins are automatically
// registered through the FlutterEngine's plugin registry
GeneratedPluginRegistrant.registerWith(engine)
}
}
}
4 changes: 4 additions & 0 deletions example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ import background_location_tracker
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)

// Register plugins for background execution - this call is kept for backward compatibility
// With modern Flutter versions (1.12+), this is not required as the plugin uses FlutterPluginBinding
BackgroundLocationTrackerPlugin.setPluginRegistrantCallback { registry in
GeneratedPluginRegistrant.register(with: registry)
}

return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Loading