fix: local sync task never runs on iOS (#21491)

* fix: local sync task never runs on iOS

* chore: rename ios register method

* refactor from using dart callback to dart entrypoint + more logs

* check if file exists before hashing

* reschedule local sync task

* chore: rename background worker logger

* refactor: move file exists check inside repo

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong 2025-09-03 02:05:58 +05:30 committed by GitHub
parent 4f7702c6bf
commit 674faf2e57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 218 additions and 152 deletions

View file

@ -62,7 +62,7 @@ private open class BackgroundWorkerPigeonCodec : StandardMessageCodec() {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface BackgroundWorkerFgHostApi {
fun enableSyncWorker()
fun enableUploadWorker(callbackHandle: Long)
fun enableUploadWorker()
fun disableUploadWorker()
companion object {
@ -93,11 +93,9 @@ interface BackgroundWorkerFgHostApi {
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enableUploadWorker$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val callbackHandleArg = args[0] as Long
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.enableUploadWorker(callbackHandleArg)
api.enableUploadWorker()
listOf(null)
} catch (exception: Throwable) {
BackgroundWorkerPigeonUtils.wrapError(exception)
@ -130,6 +128,7 @@ interface BackgroundWorkerFgHostApi {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface BackgroundWorkerBgHostApi {
fun onInitialized()
fun close()
companion object {
/** The codec used by BackgroundWorkerBgHostApi. */
@ -156,6 +155,22 @@ interface BackgroundWorkerBgHostApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.close()
listOf(null)
} catch (exception: Throwable) {
BackgroundWorkerPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}

View file

@ -11,9 +11,8 @@ import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor.DartCallback
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.embedding.engine.loader.FlutterLoader
import io.flutter.view.FlutterCallbackInformation
private const val TAG = "BackgroundWorker"
@ -58,25 +57,6 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
loader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) {
engine = FlutterEngine(ctx)
// Retrieve the callback handle stored by the main Flutter app
// This handle points to the Flutter function that should be executed in the background
val callbackHandle =
ctx.getSharedPreferences(BackgroundWorkerApiImpl.SHARED_PREF_NAME, Context.MODE_PRIVATE)
.getLong(BackgroundWorkerApiImpl.SHARED_PREF_CALLBACK_HANDLE, 0L)
if (callbackHandle == 0L) {
// Without a valid callback handle, we cannot start the Flutter background execution
complete(Result.failure())
return@ensureInitializationCompleteAsync
}
// Start the Flutter engine with the specified callback as the entry point
val callback = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle)
if (callback == null) {
complete(Result.failure())
return@ensureInitializationCompleteAsync
}
// Register custom plugins
MainActivity.registerPlugins(ctx, engine!!)
flutterApi =
@ -86,8 +66,12 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
api = this
)
engine!!.dartExecutor.executeDartCallback(
DartCallback(ctx.assets, loader.findAppBundlePath(), callback)
engine!!.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint(
loader.findAppBundlePath(),
"package:immich_mobile/domain/services/background_worker.service.dart",
"backgroundSyncNativeEntrypoint"
)
)
}
@ -109,14 +93,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
}
}
/**
* Called when the system has to stop this worker because constraints are
* no longer met or the system needs resources for more important tasks
* This is also called when the worker has been explicitly cancelled or replaced
*/
override fun onStopped() {
Log.d(TAG, "About to stop BackupWorker")
override fun close() {
if (isComplete) {
return
}
@ -134,6 +111,16 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
}, 5000)
}
/**
* Called when the system has to stop this worker because constraints are
* no longer met or the system needs resources for more important tasks
* This is also called when the worker has been explicitly cancelled or replaced
*/
override fun onStopped() {
Log.d(TAG, "About to stop BackupWorker")
close()
}
private fun handleHostResult(result: kotlin.Result<Unit>) {
if (isComplete) {
return

View file

@ -21,9 +21,8 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
Log.i(TAG, "Scheduled media observer")
}
override fun enableUploadWorker(callbackHandle: Long) {
override fun enableUploadWorker() {
updateUploadEnabled(ctx, true)
updateCallbackHandle(ctx, callbackHandle)
Log.i(TAG, "Scheduled background upload tasks")
}
@ -41,7 +40,6 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
const val SHARED_PREF_NAME = "Immich::Background"
const val SHARED_PREF_BACKUP_ENABLED = "Background::backup::enabled"
const val SHARED_PREF_CALLBACK_HANDLE = "Background::backup::callbackHandle"
private fun updateUploadEnabled(context: Context, enabled: Boolean) {
context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE).edit {
@ -49,12 +47,6 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
}
}
private fun updateCallbackHandle(context: Context, callbackHandle: Long) {
context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE).edit {
putLong(SHARED_PREF_CALLBACK_HANDLE, callbackHandle)
}
}
fun enqueueMediaObserver(ctx: Context) {
val constraints = Constraints.Builder()
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)

View file

@ -24,7 +24,7 @@ import UIKit
BackgroundServicePlugin.register(with: self.registrar(forPlugin: "BackgroundServicePlugin")!)
BackgroundServicePlugin.registerBackgroundProcessing()
BackgroundWorkerApiImpl.registerBackgroundProcessing()
BackgroundWorkerApiImpl.registerBackgroundWorkers()
BackgroundServicePlugin.setPluginRegistrantCallback { registry in
if !registry.hasPlugin("org.cocoapods.path-provider-foundation") {

View file

@ -74,7 +74,7 @@ class BackgroundWorkerPigeonCodec: FlutterStandardMessageCodec, @unchecked Senda
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol BackgroundWorkerFgHostApi {
func enableSyncWorker() throws
func enableUploadWorker(callbackHandle: Int64) throws
func enableUploadWorker() throws
func disableUploadWorker() throws
}
@ -99,11 +99,9 @@ class BackgroundWorkerFgHostApiSetup {
}
let enableUploadWorkerChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enableUploadWorker\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
enableUploadWorkerChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let callbackHandleArg = args[0] as! Int64
enableUploadWorkerChannel.setMessageHandler { _, reply in
do {
try api.enableUploadWorker(callbackHandle: callbackHandleArg)
try api.enableUploadWorker()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
@ -130,6 +128,7 @@ class BackgroundWorkerFgHostApiSetup {
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol BackgroundWorkerBgHostApi {
func onInitialized() throws
func close() throws
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@ -151,6 +150,19 @@ class BackgroundWorkerBgHostApiSetup {
} else {
onInitializedChannel.setMessageHandler(nil)
}
let closeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
closeChannel.setMessageHandler { _, reply in
do {
try api.close()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
closeChannel.setMessageHandler(nil)
}
}
}
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.

View file

@ -86,28 +86,10 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
* starts the engine, and sets up a timeout timer if specified.
*/
func run() {
// Retrieve the callback handle stored by the main Flutter app
// This handle points to the Flutter function that should be executed in the background
let callbackHandle = Int64(UserDefaults.standard.string(
forKey: BackgroundWorkerApiImpl.backgroundUploadCallbackHandleKey) ?? "0") ?? 0
if callbackHandle == 0 {
// Without a valid callback handle, we cannot start the Flutter background execution
complete(success: false)
return
}
// Use the callback handle to retrieve the actual Flutter callback information
guard let callback = FlutterCallbackCache.lookupCallbackInformation(callbackHandle) else {
// The callback handle is invalid or the callback was not found
complete(success: false)
return
}
// Start the Flutter engine with the specified callback as the entry point
let isRunning = engine.run(
withEntrypoint: callback.callbackName,
libraryURI: callback.callbackLibraryPath
withEntrypoint: "backgroundSyncNativeEntrypoint",
libraryURI: "package:immich_mobile/domain/services/background_worker.service.dart"
)
// Verify that the Flutter engine started successfully
@ -127,7 +109,7 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
if maxSeconds != nil {
// Schedule a timer to cancel the task after the specified timeout period
Timer.scheduledTimer(withTimeInterval: TimeInterval(maxSeconds!), repeats: false) { _ in
self.cancel()
self.close()
}
}
}
@ -156,7 +138,7 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
* Sends a cancel signal to the Flutter side and sets up a fallback timer to ensure
* the completion handler is eventually called even if Flutter doesn't respond.
*/
func cancel() {
func close() {
if isComplete {
return
}
@ -182,7 +164,7 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
private func handleHostResult(result: Result<Void, PigeonError>) {
switch result {
case .success(): self.complete(success: true)
case .failure(_): self.cancel()
case .failure(_): self.close()
}
}

View file

@ -6,10 +6,8 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
print("BackgroundUploadImpl:enableSyncWorker Local Sync worker scheduled")
}
func enableUploadWorker(callbackHandle: Int64) throws {
func enableUploadWorker() throws {
BackgroundWorkerApiImpl.updateUploadEnabled(true)
// Store the callback handle for later use when starting background Flutter isolates
BackgroundWorkerApiImpl.updateUploadCallbackHandle(callbackHandle)
BackgroundWorkerApiImpl.scheduleRefreshUpload()
BackgroundWorkerApiImpl.scheduleProcessingUpload()
@ -23,7 +21,6 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
}
public static let backgroundUploadEnabledKey = "immich:background:backup:enabled"
public static let backgroundUploadCallbackHandleKey = "immich:background:backup:callbackHandle"
private static let localSyncTaskID = "app.alextran.immich.background.localSync"
private static let refreshUploadTaskID = "app.alextran.immich.background.refreshUpload"
@ -33,17 +30,13 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
return UserDefaults.standard.set(isEnabled, forKey: BackgroundWorkerApiImpl.backgroundUploadEnabledKey)
}
private static func updateUploadCallbackHandle(_ callbackHandle: Int64) {
return UserDefaults.standard.set(String(callbackHandle), forKey: BackgroundWorkerApiImpl.backgroundUploadCallbackHandleKey)
}
private static func cancelUploadTasks() {
BackgroundWorkerApiImpl.updateUploadEnabled(false)
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: refreshUploadTaskID);
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: processingUploadTaskID);
}
public static func registerBackgroundProcessing() {
public static func registerBackgroundWorkers() {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: processingUploadTaskID, using: nil) { task in
if task is BGProcessingTask {
@ -102,9 +95,22 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
}
private static func handleBackgroundRefresh(task: BGAppRefreshTask, taskType: BackgroundTaskType) {
let maxSeconds: Int?
switch taskType {
case .localSync:
maxSeconds = 15
scheduleLocalSync()
case .refreshUpload:
maxSeconds = 20
scheduleRefreshUpload()
// Restrict the refresh task to run only for a maximum of 20 seconds
runBackgroundWorker(task: task, taskType: taskType, maxSeconds: 20)
case .processingUpload:
print("Unexpected background refresh task encountered")
return;
}
// Restrict the refresh task to run only for a maximum of (maxSeconds) seconds
runBackgroundWorker(task: task, taskType: taskType, maxSeconds: maxSeconds)
}
private static func handleBackgroundProcessing(task: BGProcessingTask) {
@ -134,7 +140,7 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
task.expirationHandler = {
DispatchQueue.main.async {
backgroundWorker.cancel()
backgroundWorker.close()
}
isSuccess = false

View file

@ -14,6 +14,7 @@ import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/services/auth.service.dart';
import 'package:immich_mobile/services/localization.service.dart';
@ -31,9 +32,7 @@ class BackgroundWorkerFgService {
// TODO: Move this call to native side once old timeline is removed
Future<void> enableSyncService() => _foregroundHostApi.enableSyncWorker();
Future<void> enableUploadService() => _foregroundHostApi.enableUploadWorker(
PluginUtilities.getCallbackHandle(_backgroundSyncNativeEntrypoint)!.toRawHandle(),
);
Future<void> enableUploadService() => _foregroundHostApi.enableUploadWorker();
Future<void> disableUploadService() => _foregroundHostApi.disableUploadWorker();
}
@ -44,7 +43,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
final Drift _drift;
final DriftLogger _driftLogger;
final BackgroundWorkerBgHostApi _backgroundHostApi;
final Logger _logger = Logger('BackgroundUploadBgService');
final Logger _logger = Logger('BackgroundWorkerBgService');
bool _isCleanedUp = false;
@ -66,6 +65,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
bool get _isBackupEnabled => _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
Future<void> init() async {
try {
await loadTranslations();
HttpSSLOptions.apply(applyNative: false);
await _ref.read(authServiceProvider).setOpenApiServiceEndpoint();
@ -83,12 +83,19 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
await FileDownloader().trackTasks();
configureFileDownloaderNotifications();
// Notify the host that the background upload service has been initialized and is ready to use
await _backgroundHostApi.onInitialized();
await _ref.read(fileMediaRepositoryProvider).enableBackgroundAccess();
// Notify the host that the background worker service has been initialized and is ready to use
_backgroundHostApi.onInitialized();
} catch (error, stack) {
_logger.severe("Failed to initialize background worker", error, stack);
_backgroundHostApi.close();
}
}
@override
Future<void> onLocalSync(int? maxSeconds) async {
try {
_logger.info('Local background syncing started');
final sw = Stopwatch()..start();
@ -97,6 +104,11 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
sw.stop();
_logger.info("Local sync completed in ${sw.elapsed.inSeconds}s");
} catch (error, stack) {
_logger.severe("Failed to complete local sync", error, stack);
} finally {
await _cleanup();
}
}
/* We do the following on Android upload
@ -107,16 +119,20 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
*/
@override
Future<void> onAndroidUpload() async {
try {
_logger.info('Android background processing started');
final sw = Stopwatch()..start();
await _syncAssets(hashTimeout: Duration(minutes: _isBackupEnabled ? 3 : 6));
await _handleBackup(processBulk: false);
await _cleanup();
sw.stop();
_logger.info("Android background processing completed in ${sw.elapsed.inSeconds}s");
} catch (error, stack) {
_logger.severe("Failed to complete Android background processing", error, stack);
} finally {
await _cleanup();
}
}
/* We do the following on background upload
@ -129,6 +145,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
*/
@override
Future<void> onIosUpload(bool isRefresh, int? maxSeconds) async {
try {
_logger.info('iOS background upload started with maxSeconds: ${maxSeconds}s');
final sw = Stopwatch()..start();
@ -142,16 +159,23 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
await backupFuture;
}
await _cleanup();
sw.stop();
_logger.info("iOS background upload completed in ${sw.elapsed.inSeconds}s");
} catch (error, stack) {
_logger.severe("Failed to complete iOS background upload", error, stack);
} finally {
await _cleanup();
}
}
@override
Future<void> cancel() async {
_logger.warning("Background upload cancelled");
_logger.warning("Background worker cancelled");
try {
await _cleanup();
} catch (error, stack) {
debugPrint('Failed to cleanup background worker: $error with stack: $stack');
}
}
Future<void> _cleanup() async {
@ -159,13 +183,21 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
return;
}
try {
_isCleanedUp = true;
_logger.info("Cleaning up background worker");
await _ref.read(backgroundSyncProvider).cancel();
await _ref.read(backgroundSyncProvider).cancelLocal();
if (_isar.isOpen) {
await _isar.close();
}
await _drift.close();
await _driftLogger.close();
_ref.dispose();
debugPrint("Background worker cleaned up");
} catch (error, stack) {
debugPrint('Failed to cleanup background worker: $error with stack: $stack');
}
}
Future<void> _handleBackup({bool processBulk = true}) async {
@ -221,8 +253,10 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
}
}
/// Native entry invoked from the background worker. If renaming or moving this to a different
/// library, make sure to update the entry points and URI in native workers as well
@pragma('vm:entry-point')
Future<void> _backgroundSyncNativeEntrypoint() async {
Future<void> backgroundSyncNativeEntrypoint() async {
WidgetsFlutterBinding.ensureInitialized();
DartPluginRegistrant.ensureInitialized();

View file

@ -16,6 +16,13 @@ class StorageRepository {
file = await entity?.originFile;
if (file == null) {
log.warning("Cannot get file for asset $assetId");
return null;
}
final exists = await file.exists();
if (!exists) {
log.warning("File for asset $assetId does not exist");
return null;
}
} catch (error, stackTrace) {
log.warning("Error getting file for asset $assetId", error, stackTrace);
@ -34,6 +41,13 @@ class StorageRepository {
log.warning(
"Cannot get motion file for asset ${asset.id}, name: ${asset.name}, created on: ${asset.createdAt}",
);
return null;
}
final exists = await file.exists();
if (!exists) {
log.warning("Motion file for asset ${asset.id} does not exist");
return null;
}
} catch (error, stackTrace) {
log.warning(

View file

@ -206,14 +206,14 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
WidgetsBinding.instance.addPostFrameCallback((_) {
// needs to be delayed so that EasyLocalization is working
if (Store.isBetaTimelineEnabled) {
ref.read(backgroundServiceProvider).disableService();
ref.read(driftBackgroundUploadFgService).enableSyncService();
if (ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup)) {
ref.read(backgroundServiceProvider).disableService();
ref.read(driftBackgroundUploadFgService).enableUploadService();
}
} else {
ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
ref.read(driftBackgroundUploadFgService).disableUploadService();
ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
}
});

View file

@ -82,7 +82,7 @@ class BackgroundWorkerFgHostApi {
}
}
Future<void> enableUploadWorker(int callbackHandle) async {
Future<void> enableUploadWorker() async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enableUploadWorker$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
@ -90,7 +90,7 @@ class BackgroundWorkerFgHostApi {
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[callbackHandle]);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
@ -164,6 +164,29 @@ class BackgroundWorkerBgHostApi {
return;
}
}
Future<void> close() async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
}
abstract class BackgroundWorkerFlutterApi {

View file

@ -15,8 +15,7 @@ import 'package:pigeon/pigeon.dart';
abstract class BackgroundWorkerFgHostApi {
void enableSyncWorker();
// Enables the background upload service with the given callback handle
void enableUploadWorker(int callbackHandle);
void enableUploadWorker();
// Disables the background upload service
void disableUploadWorker();
@ -27,6 +26,8 @@ abstract class BackgroundWorkerBgHostApi {
// Called from the background flutter engine when it has bootstrapped and established the
// required platform channels to notify the native side to start the background upload
void onInitialized();
void close();
}
@FlutterApi()