Skip to content

Commit 84391bb

Browse files
authored
Merge pull request #92 from DH-555/master
Improve callback handling and self-destroying
2 parents 24b287c + 67f685a commit 84391bb

7 files changed

Lines changed: 183 additions & 36 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.6.1 - 2025-07-17
2+
- Fixed callback being called once in Android (improved handling) (#91)
3+
- Fixed "FlutterJNI.loadLibrary/prefetchDefaultFontManager/init called more than once"
4+
15
## 1.6.0 - 2025-05-06
26
- Updated to use the modern FlutterPlugin.FlutterPluginBinding pattern
37
- Deprecated Registrar, PluginRegistrantCallback, and ShimPluginRegistry usage

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

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ class BackgroundLocationTrackerPlugin : FlutterPlugin, MethodCallHandler, Activi
4242
channel?.setMethodCallHandler(null)
4343
channel = null
4444
applicationContext = null
45+
46+
// Clean up the background manager when plugin is detached
47+
FlutterBackgroundManager.cleanup()
4548
}
4649

4750
override fun onMethodCall(call: MethodCall, result: Result) {
@@ -73,6 +76,7 @@ class BackgroundLocationTrackerPlugin : FlutterPlugin, MethodCallHandler, Activi
7376

7477
// New static properties for background execution
7578
private var flutterEngine: FlutterEngine? = null
79+
private var isEngineInitialized = false
7680

7781
// For compatibility with older plugins
7882
@Deprecated("Use FlutterEngine's plugin registry instead")
@@ -87,9 +91,42 @@ class BackgroundLocationTrackerPlugin : FlutterPlugin, MethodCallHandler, Activi
8791
// Method to get or create the Flutter engine for background execution
8892
@JvmStatic
8993
fun getFlutterEngine(context: Context): FlutterEngine {
90-
return flutterEngine ?: FlutterEngine(context).also {
91-
flutterEngine = it
92-
pluginRegistrantCallback?.invoke(it)
94+
synchronized(this) {
95+
if (flutterEngine == null || !isEngineInitialized) {
96+
Logger.debug(TAG, "Creating new Flutter engine for background execution")
97+
flutterEngine = FlutterEngine(context).also { engine ->
98+
pluginRegistrantCallback?.invoke(engine)
99+
isEngineInitialized = true
100+
Logger.debug(TAG, "Flutter engine created and initialized")
101+
}
102+
} else {
103+
Logger.debug(TAG, "Reusing existing Flutter engine")
104+
}
105+
return flutterEngine!!
106+
}
107+
}
108+
109+
// Method to properly cleanup the Flutter engine
110+
@JvmStatic
111+
fun cleanupFlutterEngine() {
112+
synchronized(this) {
113+
flutterEngine?.let { engine ->
114+
try {
115+
Logger.debug(TAG, "Cleaning up Flutter engine")
116+
// Stop the Dart isolate if it's running
117+
if (engine.dartExecutor.isExecutingDart) {
118+
engine.dartExecutor.onDetachedFromJNI()
119+
}
120+
// Destroy the engine
121+
engine.destroy()
122+
Logger.debug(TAG, "Flutter engine destroyed")
123+
} catch (e: Exception) {
124+
// Log the exception but don't crash
125+
Logger.debug(TAG, "Error during Flutter engine cleanup: ${e.message}")
126+
}
127+
flutterEngine = null
128+
isEngineInitialized = false
129+
}
93130
}
94131
}
95132
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ internal class MethodCallHelper(private val ctx: Context) : MethodChannel.Method
105105

106106
private fun stopTracking(ctx: Context, call: MethodCall, result: MethodChannel.Result) {
107107
serviceConnection.service?.stopTracking()
108+
FlutterBackgroundManager.cleanup()
108109
result.success(true)
109110
}
110111

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

Lines changed: 118 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,40 +19,124 @@ internal object FlutterBackgroundManager {
1919
private const val BACKGROUND_CHANNEL_NAME = "com.icapps.background_location_tracker/background_channel"
2020

2121
private val flutterLoader = FlutterLoader()
22+
private var backgroundChannel: MethodChannel? = null
23+
private var isInitialized = false
24+
private var pendingLocation: Location? = null
25+
private var isSettingUp = false
2226

2327
private fun getInitializedFlutterEngine(ctx: Context): FlutterEngine {
24-
Logger.debug("BackgroundManager", "Creating new engine")
25-
28+
Logger.debug("BackgroundManager", "Getting Flutter engine")
2629
return BackgroundLocationTrackerPlugin.getFlutterEngine(ctx)
2730
}
2831

2932
fun sendLocation(ctx: Context, location: Location) {
3033
Logger.debug("BackgroundManager", "Location: ${location.latitude}: ${location.longitude}")
31-
val engine = getInitializedFlutterEngine(ctx)
34+
35+
if (isInitialized) {
36+
// Engine is already initialized, send location immediately
37+
sendLocationToChannel(ctx, location)
38+
} else {
39+
// Store the location and initialize if needed
40+
pendingLocation = location
41+
setupBackgroundChannelIfNeeded(ctx)
42+
}
43+
}
3244

33-
val backgroundChannel = MethodChannel(engine.dartExecutor, BACKGROUND_CHANNEL_NAME)
34-
backgroundChannel.setMethodCallHandler { call, result ->
45+
private fun setupBackgroundChannelIfNeeded(ctx: Context) {
46+
if (backgroundChannel != null || isSettingUp) {
47+
Logger.debug("BackgroundManager", "Setup already in progress or completed")
48+
return // Already setup or in progress
49+
}
50+
51+
isSettingUp = true
52+
Logger.debug("BackgroundManager", "Setting up background channel and dart executor")
53+
val engine = getInitializedFlutterEngine(ctx)
54+
55+
// Check if the DartExecutor is already running to prevent the error
56+
if (engine.dartExecutor.isExecutingDart) {
57+
Logger.debug("BackgroundManager", "DartExecutor is already running, reusing existing executor")
58+
backgroundChannel = MethodChannel(engine.dartExecutor, BACKGROUND_CHANNEL_NAME)
59+
backgroundChannel?.setMethodCallHandler { call, result ->
60+
when (call.method) {
61+
"initialized" -> {
62+
Logger.debug("BackgroundManager", "Dart background isolate already initialized")
63+
isInitialized = true
64+
isSettingUp = false
65+
result.success(true)
66+
67+
// Send any pending location
68+
pendingLocation?.let { location ->
69+
Logger.debug("BackgroundManager", "Sending pending location after reuse")
70+
sendLocationToChannel(ctx, location)
71+
pendingLocation = null
72+
}
73+
}
74+
else -> {
75+
result.notImplemented()
76+
}
77+
}
78+
}
79+
80+
// Mark as initialized since the executor is already running
81+
isInitialized = true
82+
isSettingUp = false
83+
84+
// Send any pending location immediately if we have one
85+
pendingLocation?.let { location ->
86+
Logger.debug("BackgroundManager", "Sending pending location immediately (executor already running)")
87+
sendLocationToChannel(ctx, location)
88+
pendingLocation = null
89+
}
90+
91+
return
92+
}
93+
94+
backgroundChannel = MethodChannel(engine.dartExecutor, BACKGROUND_CHANNEL_NAME)
95+
backgroundChannel?.setMethodCallHandler { call, result ->
3596
when (call.method) {
36-
"initialized" -> handleInitialized(call, result, ctx, backgroundChannel, location, engine)
97+
"initialized" -> {
98+
Logger.debug("BackgroundManager", "Dart background isolate initialized")
99+
isInitialized = true
100+
isSettingUp = false
101+
result.success(true)
102+
103+
// Send any pending location
104+
pendingLocation?.let { location ->
105+
Logger.debug("BackgroundManager", "Sending pending location after initialization")
106+
sendLocationToChannel(ctx, location)
107+
pendingLocation = null
108+
}
109+
}
37110
else -> {
38111
result.notImplemented()
39-
engine.destroy()
40112
}
41113
}
42114
}
43115

44116
if (!flutterLoader.initialized()) {
117+
Logger.debug("BackgroundManager", "Initializing FlutterLoader")
45118
flutterLoader.startInitialization(ctx)
46119
}
120+
47121
flutterLoader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) {
122+
Logger.debug("BackgroundManager", "FlutterLoader initialization complete, executing Dart callback")
48123
val callbackHandle = SharedPrefsUtil.getCallbackHandle(ctx)
49124
val callbackInfo = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle)
50125
val dartBundlePath = flutterLoader.findAppBundlePath()
51-
engine.dartExecutor.executeDartCallback(DartExecutor.DartCallback(ctx.assets, dartBundlePath, callbackInfo))
126+
127+
try {
128+
engine.dartExecutor.executeDartCallback(DartExecutor.DartCallback(ctx.assets, dartBundlePath, callbackInfo))
129+
} catch (e: Exception) {
130+
Logger.debug("BackgroundManager", "Error executing Dart callback: ${e.message}")
131+
isSettingUp = false
132+
}
52133
}
53134
}
54135

55-
private fun handleInitialized(call: MethodCall, result: MethodChannel.Result, ctx: Context, channel: MethodChannel, location: Location, engine: FlutterEngine) {
136+
private fun sendLocationToChannel(ctx: Context, location: Location) {
137+
val channel = backgroundChannel ?: return
138+
Logger.debug("BackgroundManager", "Sending location to initialized channel")
139+
56140
val data = mutableMapOf<String, Any>()
57141
data["lat"] = location.latitude
58142
data["lon"] = location.longitude
@@ -73,19 +157,37 @@ internal object FlutterBackgroundManager {
73157

74158
channel.invokeMethod("onLocationUpdate", data, object : MethodChannel.Result {
75159
override fun success(result: Any?) {
76-
Logger.debug("BackgroundManager", "Got success, destroy engine!")
77-
engine.destroy()
160+
Logger.debug("BackgroundManager", "Successfully sent location update")
78161
}
79162

80163
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
81-
Logger.debug("BackgroundManager", "Got error, destroy engine! $errorCode - $errorMessage : $errorDetails")
82-
engine.destroy()
164+
Logger.debug("BackgroundManager", "Error sending location update: $errorCode - $errorMessage : $errorDetails")
83165
}
84166

85167
override fun notImplemented() {
86-
Logger.debug("BackgroundManager", "Got not implemented, destroy engine!")
87-
engine.destroy()
168+
Logger.debug("BackgroundManager", "Method not implemented for location update")
88169
}
89170
})
90171
}
91-
}
172+
173+
fun cleanup() {
174+
Logger.debug("BackgroundManager", "Cleaning up background resources")
175+
isInitialized = false
176+
isSettingUp = false
177+
backgroundChannel?.setMethodCallHandler(null)
178+
backgroundChannel = null
179+
pendingLocation = null
180+
181+
// Instead of destroying the engine completely, just mark it as not initialized
182+
// This allows for reuse without the "DartExecutor already running" error
183+
Logger.debug("BackgroundManager", "Background channel cleaned up, ready for reuse")
184+
}
185+
186+
fun forceCleanup() {
187+
Logger.debug("BackgroundManager", "Force cleaning up all background resources")
188+
cleanup()
189+
190+
// Clean up the Flutter engine completely to prevent DartExecutor reuse issues
191+
BackgroundLocationTrackerPlugin.cleanupFlutterEngine()
192+
}
193+
}

android/src/main/kotlin/com/icapps/background_location_tracker/service/LocationUpdatesService.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ internal class LocationUpdatesService : Service() {
160160
if (wakeLock?.isHeld == true) {
161161
wakeLock?.release()
162162
}
163+
164+
// Force clean up Flutter background resources when service is destroyed
165+
FlutterBackgroundManager.forceCleanup()
163166
}
164167

165168
/**

example/pubspec.lock

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ packages:
1313
dependency: transitive
1414
description:
1515
name: async
16-
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
16+
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
1717
url: "https://pub.dev"
1818
source: hosted
19-
version: "2.12.0"
19+
version: "2.13.0"
2020
background_location_tracker:
2121
dependency: "direct main"
2222
description:
2323
path: ".."
2424
relative: true
2525
source: path
26-
version: "1.5.0"
26+
version: "1.6.0"
2727
boolean_selector:
2828
dependency: transitive
2929
description:
@@ -68,10 +68,10 @@ packages:
6868
dependency: transitive
6969
description:
7070
name: fake_async
71-
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
71+
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
7272
url: "https://pub.dev"
7373
source: hosted
74-
version: "1.3.2"
74+
version: "1.3.3"
7575
ffi:
7676
dependency: transitive
7777
description:
@@ -155,10 +155,10 @@ packages:
155155
dependency: transitive
156156
description:
157157
name: leak_tracker
158-
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
158+
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
159159
url: "https://pub.dev"
160160
source: hosted
161-
version: "10.0.8"
161+
version: "10.0.9"
162162
leak_tracker_flutter_testing:
163163
dependency: transitive
164164
description:
@@ -456,10 +456,10 @@ packages:
456456
dependency: transitive
457457
description:
458458
name: vm_service
459-
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
459+
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
460460
url: "https://pub.dev"
461461
source: hosted
462-
version: "14.3.1"
462+
version: "15.0.0"
463463
web:
464464
dependency: transitive
465465
description:

pubspec.lock

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ packages:
55
dependency: transitive
66
description:
77
name: async
8-
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
8+
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
99
url: "https://pub.dev"
1010
source: hosted
11-
version: "2.12.0"
11+
version: "2.13.0"
1212
boolean_selector:
1313
dependency: transitive
1414
description:
@@ -45,10 +45,10 @@ packages:
4545
dependency: transitive
4646
description:
4747
name: fake_async
48-
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
48+
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
4949
url: "https://pub.dev"
5050
source: hosted
51-
version: "1.3.2"
51+
version: "1.3.3"
5252
flutter:
5353
dependency: "direct main"
5454
description: flutter
@@ -63,10 +63,10 @@ packages:
6363
dependency: transitive
6464
description:
6565
name: leak_tracker
66-
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
66+
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
6767
url: "https://pub.dev"
6868
source: hosted
69-
version: "10.0.8"
69+
version: "10.0.9"
7070
leak_tracker_flutter_testing:
7171
dependency: transitive
7272
description:
@@ -180,10 +180,10 @@ packages:
180180
dependency: transitive
181181
description:
182182
name: vm_service
183-
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
183+
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
184184
url: "https://pub.dev"
185185
source: hosted
186-
version: "14.3.1"
186+
version: "15.0.0"
187187
sdks:
188188
dart: ">=3.7.0-0 <4.0.0"
189189
flutter: ">=3.18.0-18.0.pre.54"

0 commit comments

Comments
 (0)