Skip to content
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
12 changes: 12 additions & 0 deletions ios/Classes/FlutterForegroundTaskPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,15 @@ + (void)setPluginRegistrantCallback:(FlutterPluginRegistrantCallback)callback {
[SwiftFlutterForegroundTaskPlugin setPluginRegistrantCallback:callback];
}
@end

// Registers the BGTaskScheduler handler at binary load time, before Flutter
// initialises. This is required for background-launch scenarios where iOS wakes
// the app to service a refresh task (e.g. via workmanager or share extension).
@interface FlutterForegroundTaskEarlyRegistration : NSObject
@end

@implementation FlutterForegroundTaskEarlyRegistration
+ (void)load {
[SwiftFlutterForegroundTaskPlugin registerAppRefreshForBackgroundLaunch];
}
@end
41 changes: 41 additions & 0 deletions ios/Classes/SceneLifecycleBridge.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Flutter
import UIKit

/// Bridges UIScene lifecycle events into a caller-supplied closure.
///
/// Registers itself with the Flutter plugin registrar via an ObjC runtime check,
/// so this plugin compiles on all Flutter versions ≥ 3.22.0 — not only those
/// that expose `addSceneDelegate` (introduced in Flutter 3.38.0).
///
/// Flutter dispatches scene lifecycle callbacks through ObjC messaging
/// (`respondsToSelector:` + `performSelector:`), so a formal
/// `FlutterSceneLifeCycleDelegate` conformance declaration is not required and
/// is intentionally omitted to avoid a compile-time dependency on that protocol.
@available(iOS 13.0, *)
class SceneLifecycleBridge: NSObject {
// Retained for the app lifetime; released only if a new bridge is registered.
private static var instance: SceneLifecycleBridge?

private let onSceneDidEnterBackground: () -> Void

private init(onSceneDidEnterBackground: @escaping () -> Void) {
self.onSceneDidEnterBackground = onSceneDidEnterBackground
}

/// Instantiates a bridge and registers it as a Flutter scene delegate.
/// Silently no-ops on Flutter versions that do not expose `addSceneDelegate`.
static func register(
with registrar: FlutterPluginRegistrar,
onSceneDidEnterBackground: @escaping () -> Void
) {
let sel = NSSelectorFromString("addSceneDelegate:")
guard (registrar as AnyObject).responds(to: sel) else { return }
let bridge = SceneLifecycleBridge(onSceneDidEnterBackground: onSceneDidEnterBackground)
instance = bridge
_ = (registrar as AnyObject).perform(sel, with: bridge)
}

@objc func sceneDidEnterBackground(_: UIScene) {
onSceneDidEnterBackground()
}
}
17 changes: 17 additions & 0 deletions ios/Classes/SwiftFlutterForegroundTaskPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,25 @@ public class SwiftFlutterForegroundTaskPlugin: NSObject, FlutterPlugin {
instance.initServices()
instance.initChannels(registrar.messenger())
registrar.addApplicationDelegate(instance)
if #available(iOS 13.0, *) {
SceneLifecycleBridge.register(with: registrar, onSceneDidEnterBackground: scheduleAppRefresh)
}
}

public static func setPluginRegistrantCallback(_ callback: @escaping FlutterPluginRegistrantCallback) {
registerPlugins = callback
}

/// Called from the ObjC `+load` hook so that the BGTaskScheduler handler is
/// registered at binary load time — before Flutter initialises — which is
/// required when iOS wakes the app in the background to service a refresh task.
@objc public static func registerAppRefreshForBackgroundLaunch() {
if #available(iOS 13.0, *) {
let permitted = Bundle.main.object(forInfoDictionaryKey: "BGTaskSchedulerPermittedIdentifiers") as? [String] ?? []
guard permitted.contains(refreshIdentifier) else { return }
registerAppRefresh()
}
}

public static func addTaskLifecycleListener(_ listener: FlutterForegroundTaskLifecycleListener) {
BackgroundService.sharedInstance.addTaskLifecycleListener(listener)
Expand Down Expand Up @@ -132,9 +146,12 @@ public class SwiftFlutterForegroundTaskPlugin: NSObject, FlutterPlugin {

// ============== Background App Refresh ==============
public static var refreshIdentifier: String = "com.pravera.flutter_foreground_task.refresh"
private static var isBgTaskRegistered: Bool = false

@available(iOS 13.0, *)
private static func registerAppRefresh() {
guard !isBgTaskRegistered else { return }
isBgTaskRegistered = true
BGTaskScheduler.shared.register(forTaskWithIdentifier: refreshIdentifier, using: nil) { task in
handleAppRefresh(task: task as! BGAppRefreshTask)
}
Expand Down
Loading