Skip to content
Open
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
5 changes: 3 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ group 'yukams.app.background_locator_2'
version '1.0-SNAPSHOT'

buildscript {
ext.kotlin_version = '1.4.21'
ext.kotlin_version = '1.5.20'
repositories {
google()
jcenter()
Expand All @@ -25,7 +25,8 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 29
namespace 'yukams.app.background_locator_2'
compileSdkVersion 31

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import android.util.Log
import com.google.android.gms.location.LocationRequest
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineGroup
import io.flutter.embedding.engine.FlutterEngineGroupCache
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.MethodChannel
import io.flutter.view.FlutterCallbackInformation
Expand All @@ -34,7 +36,6 @@ internal fun IsolateHolderService.startLocatorService(context: Context) {
// We need flutter engine to handle callback, so if it is not available we have to create a
// Flutter engine without any view
Log.e("IsolateHolderService", "startLocatorService: Start Flutter Engine")
IsolateHolderService.backgroundEngine = FlutterEngine(context)

val callbackHandle = context.getSharedPreferences(
Keys.SHARED_PREFERENCES_KEY,
Expand All @@ -49,12 +50,28 @@ internal fun IsolateHolderService.startLocatorService(context: Context) {
return;
}

val args = DartExecutor.DartCallback(
context.assets,
val dartEntrypoint = DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
callbackInfo
"package:background_locator_2/background_locator.dart",
"entrypoint"
)
IsolateHolderService.backgroundEngine?.dartExecutor?.executeDartCallback(args)
val args = mutableListOf<String>()
args.add(callbackHandle.toString())
val engineGroupCache = FlutterEngineGroupCache.getInstance()
if (engineGroupCache.contains("main")) {
engineGroupCache.remove("main")
}
val engineGroup = engineGroupCache.get("main") ?: run {
val newEngineGroup = FlutterEngineGroup(this)
engineGroupCache.put("main", newEngineGroup)
newEngineGroup
}
IsolateHolderService.backgroundEngine = engineGroup.createAndRunEngine(
FlutterEngineGroup.Options(this)
.setDartEntrypoint(dartEntrypoint)
.setDartEntrypointArgs(args)
)

isServiceInitialized = true
Log.e("IsolateHolderExtension", "service initialized")
}
Expand Down
66 changes: 54 additions & 12 deletions ios/Classes/BackgroundLocatorPlugin.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import "BackgroundLocatorPlugin.h"
#import "SwizzledFlutterEngineGroupCache.h"
#import "Globals.h"
#import "Utils/Util.h"
#import "Preferences/PreferencesManager.h"
Expand All @@ -12,6 +13,7 @@ @implementation BackgroundLocatorPlugin {
NSObject<FlutterPluginRegistrar> *_registrar;
CLLocationManager *_locationManager;
CLLocation* _lastLocation;
id _backgroundActivitySession; // CLBackgroundActivitySession (iOS 17+)
}

static FlutterPluginRegistrantCallback registerPlugins = nil;
Expand Down Expand Up @@ -80,6 +82,12 @@ - (void)applicationDidEnterBackground:(UIApplication *)application {

-(void)applicationWillTerminate:(UIApplication *)application {
[self observeRegionForLocation:_lastLocation];
if (@available(iOS 17.0, *)) {
if (_backgroundActivitySession != nil) {
[(CLBackgroundActivitySession *)_backgroundActivitySession invalidate];
_backgroundActivitySession = nil;
}
}
if([PreferencesManager isStopWithTerminate]){
[self removeLocator];
}
Expand Down Expand Up @@ -136,25 +144,24 @@ - (void) sendLocationEvent: (NSDictionary<NSString*,NSNumber*>*)location {

- (instancetype)init:(NSObject<FlutterPluginRegistrar> *)registrar {
self = [super init];
_headlessRunner = [[FlutterEngine alloc] initWithName:@"LocatorIsolate" project:nil allowHeadlessExecution:YES];

_headlessRunner = nil;
_registrar = registrar;
[self prepareLocationManager];

_mainChannel = [FlutterMethodChannel methodChannelWithName:kChannelId
binaryMessenger:[registrar messenger]];
[registrar addMethodCallDelegate:self channel:_mainChannel];

_callbackChannel =
[FlutterMethodChannel methodChannelWithName:kBackgroundChannelId
binaryMessenger:[_headlessRunner binaryMessenger] ];
return self;
}

- (void) prepareLocationManager {
_locationManager = [[CLLocationManager alloc] init];
[_locationManager setDelegate:self];
_locationManager.pausesLocationUpdatesAutomatically = NO;
if (@available(iOS 12.0, *)) {
_locationManager.activityType = CLActivityTypeAirborne;
}
}

#pragma mark MethodCallHelperDelegate
Expand All @@ -163,10 +170,33 @@ - (void)startLocatorService:(int64_t)handle {
[PreferencesManager setCallbackDispatcherHandle:handle];
FlutterCallbackInformation *info = [FlutterCallbackCache lookupCallbackInformation:handle];
NSAssert(info != nil, @"failed to find callback");

NSString *entrypoint = info.callbackName;
NSString *uri = info.callbackLibraryPath;
[_headlessRunner runWithEntrypoint:entrypoint libraryURI:uri];
NSString *groupName = @"main";
FlutterEngineGroup *engineGroup = nil;
SwizzledFlutterEngineGroupCache *engineGroupCache = [SwizzledFlutterEngineGroupCache sharedInstance];
if (engineGroupCache) {
engineGroup = [engineGroupCache get:groupName];
}
if (!engineGroup) {
FlutterEngineGroup *newEngineGroup = [[FlutterEngineGroup alloc] initWithName:groupName project:nil];
[engineGroupCache put:groupName engineGroup:newEngineGroup];
engineGroup = newEngineGroup;
}
if (_headlessRunner == nil) {
NSString *handleString = [NSString stringWithFormat:@"%lld", handle];
NSArray *args = @[handleString];
FlutterEngineGroupOptions* options = [[FlutterEngineGroupOptions alloc] init];
options.entrypoint = entrypoint;
options.libraryURI = uri;
options.initialRoute = nil;
options.entrypointArgs = args;
_headlessRunner = [engineGroup makeEngineWithOptions:options];
}
_callbackChannel =
[FlutterMethodChannel methodChannelWithName:kBackgroundChannelId
binaryMessenger:[_headlessRunner binaryMessenger] ];
NSAssert(registerPlugins != nil, @"failed to set registerPlugins");

// Once our headless runner has been started, we need to register the application's plugins
Expand Down Expand Up @@ -216,8 +246,14 @@ - (void)registerLocator:(int64_t)callback
DisposePluggable *disposePluggable = [[DisposePluggable alloc] init];
[disposePluggable setCallback:disposeCallback];

if (@available(iOS 17.0, *)) {
if (_backgroundActivitySession == nil) {
_backgroundActivitySession = [CLBackgroundActivitySession backgroundActivitySession];
NSLog(@"BackgroundLocator: started CLBackgroundActivitySession");
}
}

[_locationManager startUpdatingLocation];
[_locationManager startMonitoringSignificantLocationChanges];
}

- (void)removeLocator {
Expand All @@ -232,12 +268,18 @@ - (void)removeLocator {
_locationManager.allowsBackgroundLocationUpdates = NO;
}

[_locationManager stopMonitoringSignificantLocationChanges];

for (CLRegion* region in [_locationManager monitoredRegions]) {
[_locationManager stopMonitoringForRegion:region];
}
}

if (@available(iOS 17.0, *)) {
if (_backgroundActivitySession != nil) {
[(CLBackgroundActivitySession *)_backgroundActivitySession invalidate];
_backgroundActivitySession = nil;
NSLog(@"BackgroundLocator: invalidated CLBackgroundActivitySession");
}
}

DisposePluggable *disposePluggable = [[DisposePluggable alloc] init];
[disposePluggable onServiceDispose];
Expand Down
11 changes: 11 additions & 0 deletions ios/Classes/SwizzledFlutterEngineGroupCache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
#import <Flutter/FlutterEngineGroup.h>

@interface SwizzledFlutterEngineGroupCache : NSObject

+ (instancetype)sharedInstance;
- (FlutterEngineGroup *)get:(NSString *)key;
- (void)put:(NSString *)key engineGroup:(FlutterEngineGroup *)engineGroup;

@end
31 changes: 31 additions & 0 deletions ios/Classes/SwizzledFlutterEngineGroupCache.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#import "SwizzledFlutterEngineGroupCache.h"
#import <objc/runtime.h>

@implementation SwizzledFlutterEngineGroupCache

+ (instancetype)sharedInstance {
// Retrieve the original class by name using reflection
Class originalClass = NSClassFromString(@"SwizzlableFlutterEngineGroupCache");

// Check if the class exists
if (originalClass && [originalClass respondsToSelector:@selector(sharedInstance)]) {
// Call the original sharedInstance method dynamically
id sharedInstance = [originalClass performSelector:@selector(sharedInstance)];
return sharedInstance;
}

// Return nil or handle failure if the class or method is not found
NSLog(@"Original class or sharedInstance method not found");
return nil;
}

- (FlutterEngineGroup *)get:(NSString *)key {
// Interface
return nil;
}

- (void)put:(NSString *)key engineGroup:(FlutterEngineGroup *)engineGroup {
// Interface
}

@end
11 changes: 11 additions & 0 deletions lib/background_locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ import 'callback_dispatcher.dart';
import 'keys.dart';
import 'location_dto.dart';

@pragma('vm:entry-point')
Future<void> entrypoint(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized();
final int handle = int.parse(args.first);
final callbackHandle = CallbackHandle.fromRawHandle(handle);
final callback = PluginUtilities.getCallbackFromHandle(callbackHandle);
if (callback != null) {
callback();
}
}

class BackgroundLocator {
static const MethodChannel _channel = const MethodChannel(Keys.CHANNEL_ID);

Expand Down