Skip to content

Commit 545336c

Browse files
committed
Update to Flutter Plugin V2 embedding; deprecate old methods for background execution
1 parent e0316f0 commit 545336c

File tree

10 files changed

+113
-71
lines changed

10 files changed

+113
-71
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 1.6.0 - 2025-05-06
2+
- Updated to use the modern FlutterPlugin.FlutterPluginBinding pattern
3+
- Deprecated Registrar, PluginRegistrantCallback, and ShimPluginRegistry usage
4+
- Added backward compatibility for older Flutter versions
5+
- Improved handling of plugin registration in background execution
6+
17
## 1.5.0 - 2025-01-29
28
- Updated gradle dependencies
39

README.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ Or add the info to the Info.plist
5353

5454
### Update the AppDelegate
5555

56-
Make sure you call the `setPluginRegistrantCallback` so other plugins can be accessed in the background.
57-
5856
```
5957
import UIKit
6058
import Flutter
@@ -65,6 +63,8 @@ import background_location_tracker
6563
override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
6664
GeneratedPluginRegistrant.register(with: self)
6765
66+
// Register plugins for background execution - this method is deprecated
67+
// but kept for backward compatibility
6868
BackgroundLocationTrackerPlugin.setPluginRegistrantCallback { registry in
6969
GeneratedPluginRegistrant.register(with: registry)
7070
}
@@ -124,7 +124,21 @@ Future stopLocationTracking() async {
124124

125125
```
126126
This is mostly caused by a misconfiguration of the plugin:
127-
Android Pre v2 embedding: make sure the plugin registrant callback is set
128-
Android v2 embedding: Log a new github issues. This
129-
iOS: make sure the plugin registrant callback is set
127+
- Android: This plugin now uses Flutter's Plugin V2 embedding pattern (FlutterPluginBinding) so you don't need to set a manual plugin registrant callback.
128+
- iOS: For compatibility with older Flutter versions, the setPluginRegistrantCallback is still available but will be removed in a future version.
129+
130+
If you're using Flutter 1.12 or later, the plugin should work out of the box with the V2 embedding.
130131
```
132+
133+
## Migration Guide
134+
135+
### Upgrading from older versions
136+
137+
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.
138+
139+
For backward compatibility, the older methods are still available but marked as deprecated:
140+
141+
- `BackgroundLocationTrackerPlugin.setPluginRegistrantCallback` on Android
142+
- `BackgroundLocationTrackerPlugin.setPluginRegistrantCallback` on iOS
143+
144+
These methods will be removed in a future version of the plugin.

android/src/main/kotlin/com/icapps/background_location_tracker/BackgroundLocationTrackerPlugin.kt

Lines changed: 39 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,29 @@ import io.flutter.plugin.common.MethodCall
1919
import io.flutter.plugin.common.MethodChannel
2020
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
2121
import io.flutter.plugin.common.MethodChannel.Result
22-
import io.flutter.plugin.common.PluginRegistry
22+
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
23+
import io.flutter.embedding.engine.FlutterEngine
2324

2425
class BackgroundLocationTrackerPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
2526
private var lifecycle: Lifecycle? = null
2627
private var methodCallHelper: MethodCallHelper? = null
27-
28-
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
29-
registerBackgroundLocationManager(binding.binaryMessenger, binding.applicationContext)
28+
private var channel: MethodChannel? = null
29+
private var applicationContext: Context? = null
30+
31+
override fun onAttachedToEngine(binding: FlutterPluginBinding) {
32+
applicationContext = binding.applicationContext
33+
channel = MethodChannel(binding.binaryMessenger, FOREGROUND_CHANNEL_NAME)
34+
channel?.setMethodCallHandler(this)
35+
36+
if (methodCallHelper == null) {
37+
methodCallHelper = MethodCallHelper.getInstance(binding.applicationContext)
38+
}
3039
}
3140

32-
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
41+
override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
42+
channel?.setMethodCallHandler(null)
43+
channel = null
44+
applicationContext = null
3345
}
3446

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

3951
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
4052
lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding)
41-
if (methodCallHelper == null) {
42-
ActivityCounter.attach(binding.activity)
43-
methodCallHelper = MethodCallHelper.getInstance(binding.activity.applicationContext)
44-
}
53+
ActivityCounter.attach(binding.activity)
4554
methodCallHelper?.let {
4655
lifecycle?.removeObserver(it)
4756
lifecycle?.addObserver(it)
@@ -50,67 +59,42 @@ class BackgroundLocationTrackerPlugin : FlutterPlugin, MethodCallHandler, Activi
5059

5160
override fun onDetachedFromActivity() {}
5261

53-
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {}
62+
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
63+
onAttachedToActivity(binding)
64+
}
5465

55-
override fun onDetachedFromActivityForConfigChanges() {}
66+
override fun onDetachedFromActivityForConfigChanges() {
67+
onDetachedFromActivity()
68+
}
5669

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

61-
var pluginRegistryCallback: PluginRegistry.PluginRegistrantCallback? = null
74+
// New static properties for background execution
75+
private var flutterEngine: FlutterEngine? = null
6276

63-
@JvmStatic
64-
private fun registerBackgroundLocationManager(messenger: BinaryMessenger, ctx: Context) {
65-
val channel = MethodChannel(messenger, FOREGROUND_CHANNEL_NAME)
66-
channel.setMethodCallHandler(BackgroundLocationTrackerPlugin().apply {
67-
if (methodCallHelper == null) {
68-
methodCallHelper = MethodCallHelper.getInstance(ctx)
69-
}
70-
methodCallHelper?.let {
71-
lifecycle?.removeObserver(it)
72-
lifecycle?.addObserver(it)
73-
}
74-
})
75-
}
77+
// For compatibility with older plugins
78+
@Deprecated("Use FlutterEngine's plugin registry instead")
79+
private var pluginRegistrantCallback: ((FlutterEngine) -> Unit)? = null
7680

7781
@JvmStatic
78-
fun registerWith(registrar: PluginRegistry.Registrar) {
79-
val activity = registrar.activity()
80-
if (activity == null) {
81-
Logger.debug(TAG, "Activity should not be null while registering this plugin")
82-
return
83-
}
84-
85-
val lifecycle: Lifecycle = if (activity is LifecycleOwner) {
86-
(activity as LifecycleOwner).lifecycle
87-
} else {
88-
Logger.debug(TAG, "Your activity has not implemented a lifecycle owner. We will create one for you.")
89-
@Suppress("DEPRECATION")
90-
ProxyLifecycleProvider(activity).lifecycle
91-
}
92-
93-
ActivityCounter.attach(activity)
94-
val channel = MethodChannel(registrar.messenger(), FOREGROUND_CHANNEL_NAME)
95-
channel.setMethodCallHandler(BackgroundLocationTrackerPlugin().apply {
96-
if (methodCallHelper == null) {
97-
methodCallHelper = MethodCallHelper.getInstance(registrar.activeContext())
98-
}
99-
methodCallHelper?.let {
100-
lifecycle.removeObserver(it)
101-
lifecycle.addObserver(it)
102-
}
103-
})
82+
@Deprecated("Use the Android embedding v2 instead")
83+
fun setPluginRegistrantCallback(callback: ((FlutterEngine) -> Unit)) {
84+
pluginRegistrantCallback = callback
10485
}
10586

106-
@Deprecated(message = "Use the Android v2 embedding method.")
87+
// Method to get or create the Flutter engine for background execution
10788
@JvmStatic
108-
fun setPluginRegistrantCallback(pluginRegistryCallback: PluginRegistry.PluginRegistrantCallback) {
109-
BackgroundLocationTrackerPlugin.pluginRegistryCallback = pluginRegistryCallback
89+
fun getFlutterEngine(context: Context): FlutterEngine {
90+
return flutterEngine ?: FlutterEngine(context).also {
91+
flutterEngine = it
92+
pluginRegistrantCallback?.invoke(it)
93+
}
11094
}
11195
}
11296

113-
@Deprecated(message = "Use the Android v2 embedding method.")
97+
@Deprecated("Use the Android embedding v2 instead")
11498
private class ProxyLifecycleProvider internal constructor(activity: Activity) : Application.ActivityLifecycleCallbacks, LifecycleOwner {
11599
override val lifecycle = LifecycleRegistry(this)
116100
private val registrarActivityHashCode: Int = activity.hashCode()

android/src/main/kotlin/com/icapps/background_location_tracker/flutter/FlutterBackgroundManager.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import com.icapps.background_location_tracker.utils.SharedPrefsUtil
1111
import io.flutter.embedding.engine.FlutterEngine
1212
import io.flutter.embedding.engine.dart.DartExecutor
1313
import io.flutter.embedding.engine.loader.FlutterLoader
14-
import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry
1514
import io.flutter.plugin.common.MethodCall
1615
import io.flutter.plugin.common.MethodChannel
1716
import io.flutter.view.FlutterCallbackInformation
@@ -24,10 +23,7 @@ internal object FlutterBackgroundManager {
2423
private fun getInitializedFlutterEngine(ctx: Context): FlutterEngine {
2524
Logger.debug("BackgroundManager", "Creating new engine")
2625

27-
val engine = FlutterEngine(ctx)
28-
//Backwards compatibility with v1. We register all the user's plugins.
29-
BackgroundLocationTrackerPlugin.pluginRegistryCallback?.registerWith(ShimPluginRegistry(engine))
30-
return engine
26+
return BackgroundLocationTrackerPlugin.getFlutterEngine(ctx)
3127
}
3228

3329
fun sendLocation(ctx: Context, location: Location) {

example/android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
22
<application
33
android:label="example"
4-
android:name="${applicationName}"
4+
android:name=".Application"
55
android:icon="@mipmap/ic_launcher">
66
<activity
77
android:name=".MainActivity"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.example.example
2+
3+
import io.flutter.app.FlutterApplication
4+
import com.icapps.background_location_tracker.BackgroundLocationTrackerPlugin
5+
import io.flutter.plugin.common.PluginRegistry
6+
import io.flutter.plugins.GeneratedPluginRegistrant
7+
8+
// This Application class shows both approaches:
9+
// 1. The deprecated approach using PluginRegistrantCallback (for backward compatibility)
10+
// 2. Comments showing that this is not needed with the modern FlutterPluginBinding approach
11+
class Application : FlutterApplication() {
12+
override fun onCreate() {
13+
super.onCreate()
14+
15+
// Note: This is the deprecated approach and is only kept for backward compatibility
16+
// With modern Flutter plugins using the v2 embedding (FlutterPluginBinding),
17+
// this callback registration is not necessary
18+
BackgroundLocationTrackerPlugin.setPluginRegistrantCallback { engine ->
19+
// In a modern plugin using FlutterPluginBinding, plugins are automatically
20+
// registered through the FlutterEngine's plugin registry
21+
GeneratedPluginRegistrant.registerWith(engine)
22+
}
23+
}
24+
}

example/ios/Runner/AppDelegate.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ import background_location_tracker
99
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
1010
) -> Bool {
1111
GeneratedPluginRegistrant.register(with: self)
12+
13+
// Register plugins for background execution - this call is kept for backward compatibility
14+
// With modern Flutter versions (1.12+), this is not required as the plugin uses FlutterPluginBinding
1215
BackgroundLocationTrackerPlugin.setPluginRegistrantCallback { registry in
1316
GeneratedPluginRegistrant.register(with: registry)
1417
}
18+
1519
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
1620
}
1721
}

ios/Classes/BackgroundLocationTrackerPlugin.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
1414
[SwiftBackgroundLocationTrackerPlugin registerWithRegistrar:registrar];
1515
}
1616

17+
// This method is deprecated and will be removed in a future version
18+
// Use the iOS embedding v2 method instead
1719
+ (void)setPluginRegistrantCallback:(FlutterPluginRegistrantCallback)callback {
1820
[SwiftBackgroundLocationTrackerPlugin setPluginRegistrantCallback:callback];
1921
}

ios/Classes/ForegroundChannel.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,15 @@ public class ForegroundChannel : NSObject {
2525

2626
private let userDefaults = UserDefaults.standard
2727

28+
// This method is kept for backwards compatibility but marked as deprecated
29+
@available(*, deprecated, message: "Use createMethodChannel(binaryMessenger:) instead")
2830
public static func getMethodChannel(with registrar: FlutterPluginRegistrar) -> FlutterMethodChannel {
29-
return FlutterMethodChannel(name: FOREGROUND_CHANNEL_NAME, binaryMessenger: registrar.messenger())
31+
return createMethodChannel(binaryMessenger: registrar.messenger())
32+
}
33+
34+
// New method that works with FlutterBinaryMessenger directly
35+
public static func createMethodChannel(binaryMessenger: FlutterBinaryMessenger) -> FlutterMethodChannel {
36+
return FlutterMethodChannel(name: FOREGROUND_CHANNEL_NAME, binaryMessenger: binaryMessenger)
3037
}
3138

3239
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {

ios/Classes/SwiftBackgroundLocationTrackerPlugin.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ public class SwiftBackgroundLocationTrackerPlugin: FlutterPluginAppLifeCycleDele
1717
private static var initializedBackgroundCallbacksStarted = false
1818
private static var locationData: [String: Any]? = nil
1919

20-
private static var flutterPluginRegistrantCallback: FlutterPluginRegistrantCallback?
20+
// This will store the plugin that registered engines
21+
private static var pluginRegistrants: [(FlutterEngine) -> Void] = []
2122

2223
private let locationManager = LocationManager.shared()
2324

@@ -27,12 +28,13 @@ extension SwiftBackgroundLocationTrackerPlugin: FlutterPlugin {
2728

2829
@objc
2930
public static func setPluginRegistrantCallback(_ callback: @escaping FlutterPluginRegistrantCallback) {
30-
flutterPluginRegistrantCallback = callback
31+
// Store the callback in our new pluginRegistrants array
32+
pluginRegistrants.append(callback)
3133
}
3234

3335
public static func register(with registrar: FlutterPluginRegistrar) {
3436
foregroundChannel = ForegroundChannel()
35-
let methodChannel = ForegroundChannel.getMethodChannel(with: registrar)
37+
let methodChannel = ForegroundChannel.createMethodChannel(binaryMessenger: registrar.messenger())
3638
let instance = SwiftBackgroundLocationTrackerPlugin()
3739
registrar.addMethodCallDelegate(instance, channel: methodChannel)
3840
registrar.addApplicationDelegate(instance)
@@ -61,7 +63,10 @@ extension SwiftBackgroundLocationTrackerPlugin: FlutterPlugin {
6163

6264
CustomLogger.log(message: "FlutterEngine.run returned `\(success)`")
6365
if success {
64-
SwiftBackgroundLocationTrackerPlugin.flutterPluginRegistrantCallback?(flutterEngine)
66+
// Run all the registered plugin registrants
67+
for registrant in pluginRegistrants {
68+
registrant(flutterEngine)
69+
}
6570
self.flutterEngine = flutterEngine
6671
} else {
6772
CustomLogger.log(message: "FlutterEngine.run returned `false` we will cleanup the flutterEngine")

0 commit comments

Comments
 (0)