├── ios ├── Assets │ └── .gitkeep ├── Classes │ ├── pluggables │ │ ├── InitPluggable.h │ │ ├── DisposePluggable.h │ │ ├── Pluggable.h │ │ ├── DisposePluggable.m │ │ └── InitPluggable.m │ ├── BackgroundLocatorPlugin.h │ ├── Utils │ │ ├── Util.h │ │ └── Util.m │ ├── Preferences │ │ ├── PreferencesManager.h │ │ └── PreferencesManager.m │ ├── Helpers │ │ ├── MethodCallHelper.h │ │ └── MethodCallHelper.m │ ├── Globals.h │ ├── Globals.m │ └── BackgroundLocatorPlugin.m ├── background_locator.podspec └── .gitignore ├── res └── values │ └── strings_en.arb ├── android ├── settings.gradle ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── rekab │ │ └── app │ │ └── background_locator │ │ ├── provider │ │ ├── LocationRequestOptions.kt │ │ ├── LocationUpdateListener.kt │ │ ├── LocationClient.kt │ │ ├── BLLocationProvider.kt │ │ ├── GoogleLocationProviderClient.kt │ │ ├── LocationParserUtil.kt │ │ └── AndroidLocationProviderClient.kt │ │ ├── pluggables │ │ ├── Pluggable.kt │ │ ├── DisposePluggable.kt │ │ └── InitPluggable.kt │ │ ├── BootBroadcastReceiver.kt │ │ ├── IsolateHolderExtension.kt │ │ ├── Keys.kt │ │ ├── PreferencesManager.kt │ │ ├── IsolateHolderService.kt │ │ └── BackgroundLocatorPlugin.kt ├── .gitignore ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .settings │ └── org.eclipse.buildship.core.prefs ├── .project └── build.gradle ├── demo.gif ├── example ├── ios │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ └── project.pbxproj │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── .gitignore │ └── Podfile ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_location.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_location.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_location.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_location.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_location.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── rekab │ │ │ │ │ │ └── app │ │ │ │ │ │ └── background_locator_example │ │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ │ └── Application.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── .metadata ├── README.md ├── lib │ ├── file_manager.dart │ ├── location_callback_handler.dart │ ├── location_service_repository.dart │ └── main.dart ├── test │ └── widget_test.dart ├── .gitignore └── pubspec.yaml ├── .metadata ├── test └── background_locator_test.dart ├── lib ├── auto_stop_handler.dart ├── settings │ ├── locator_settings.dart │ ├── ios_settings.dart │ └── android_settings.dart ├── location_dto.dart ├── callback_dispatcher.dart ├── utils │ └── settings_util.dart ├── background_locator.dart └── keys.dart ├── pubspec.yaml ├── .github ├── workflows │ ├── build_pr.yml │ └── build.yml └── no-response.yml ├── LICENSE ├── .gitignore ├── README.md └── CHANGELOG.md /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/values/strings_en.arb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'background_locator' 2 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/demo.gif -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_location.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_location.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_location.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_location.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_location.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/provider/LocationRequestOptions.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator.provider 2 | 3 | class LocationRequestOptions(val interval: Long, val accuracy: Int, val distanceFilter: Float) -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/rekab/app/background_locator_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity : FlutterActivity() -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/provider/LocationUpdateListener.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator.provider 2 | 3 | import java.util.HashMap 4 | 5 | interface LocationUpdateListener { 6 | fun onLocationUpdated(location: HashMap?) 7 | } -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 09 11:35:09 IRST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 7 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jan 04 12:08:15 IRST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 7 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/provider/LocationClient.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator.provider 2 | 3 | enum class LocationClient(val value: Int) { 4 | Google(0), Android(1); 5 | 6 | companion object { 7 | fun fromInt(value: Int) = values().firstOrNull { it.value == value } 8 | } 9 | } -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/provider/BLLocationProvider.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator.provider 2 | 3 | interface BLLocationProvider { 4 | var listener: LocationUpdateListener? 5 | 6 | fun removeLocationUpdates() 7 | 8 | fun requestLocationUpdates(request: LocationRequestOptions) 9 | } 10 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 2d2a1ffec95cc70a3218872a2cd3f8de4933c42f 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /ios/Classes/pluggables/InitPluggable.h: -------------------------------------------------------------------------------- 1 | // 2 | // InitPluggable.h 3 | // background_locator 4 | // 5 | // Created by Mehdok on 6/7/21. 6 | // 7 | 8 | #import 9 | #import "Pluggable.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface InitPluggable : NSObject 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 2d2a1ffec95cc70a3218872a2cd3f8de4933c42f 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /ios/Classes/pluggables/DisposePluggable.h: -------------------------------------------------------------------------------- 1 | // 2 | // DisposePluggable.h 3 | // background_locator 4 | // 5 | // Created by Mehdok on 6/7/21. 6 | // 7 | 8 | #import 9 | #import "Pluggable.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DisposePluggable : NSObject 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/pluggables/Pluggable.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator.pluggables 2 | 3 | import android.content.Context 4 | 5 | interface Pluggable { 6 | fun setCallback(context: Context, callbackHandle: Long) 7 | fun onServiceStart(context: Context) { /*optional*/ } 8 | fun onServiceDispose(context: Context) {/*optional*/ } 9 | } -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Classes/pluggables/Pluggable.h: -------------------------------------------------------------------------------- 1 | // 2 | // Pluggable.h 3 | // Pods 4 | // 5 | // Created by Mehdok on 6/6/21. 6 | // 7 | 8 | #ifndef Pluggable_h 9 | #define Pluggable_h 10 | 11 | @protocol Pluggable 12 | - (void) setCallback:(int64_t) callbackHandle; 13 | - (void) onServiceStart: (NSDictionary*)initialDataDictionary; 14 | - (void) onServiceDispose; 15 | @end 16 | 17 | #endif /* Pluggable_h */ 18 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Classes/BackgroundLocatorPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "MethodCallHelper.h" 4 | 5 | @interface BackgroundLocatorPlugin : NSObject 6 | 7 | + (BackgroundLocatorPlugin*_Nullable) getInstance; 8 | - (void)invokeMethod:(NSString*_Nonnull)method arguments:(id _Nullable)arguments; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments= 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.0)) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=/usr/lib/jvm/java-8-openjdk-amd64 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /test/background_locator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | const MethodChannel channel = MethodChannel('background_locator'); 6 | 7 | setUp(() { 8 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 9 | return '42'; 10 | }); 11 | }); 12 | 13 | tearDown(() { 14 | channel.setMockMethodCallHandler(null); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /ios/Classes/Utils/Util.h: -------------------------------------------------------------------------------- 1 | // 2 | // Util.h 3 | // background_locator 4 | // 5 | // Created by Mehdi Sohrabi on 6/28/20. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface Util : NSObject 14 | 15 | + (CLLocationAccuracy) getAccuracy:(long)key; 16 | + (NSDictionary*) getLocationMap:(CLLocation *)location; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android__ 4 | Project android__ created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/BootBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | 7 | class BootBroadcastReceiver : BroadcastReceiver() { 8 | override fun onReceive(context: Context, intent: Intent) { 9 | if (intent.action == "android.intent.action.BOOT_COMPLETED") { 10 | BackgroundLocatorPlugin.registerAfterBoot(context) 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /lib/auto_stop_handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'background_locator.dart'; 4 | 5 | class AutoStopHandler extends WidgetsBindingObserver { 6 | @override 7 | Future didChangeAppLifecycleState(AppLifecycleState state) async { 8 | switch (state) { 9 | case AppLifecycleState.inactive: 10 | case AppLifecycleState.paused: 11 | case AppLifecycleState.detached: 12 | await BackgroundLocator.unRegisterLocationUpdate(); 13 | break; 14 | case AppLifecycleState.resumed: 15 | break; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # background_locator_example 2 | 3 | Demonstrates how to use the background_locator plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: background_locator 2 | description: A Flutter plugin to request the location even if the app is killed. Sending the location to a dart function in background, also provide a meter filter 3 | version: 1.6.12 4 | homepage: https://github.com/rekab-app/background_locator 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | flutter: ">=1.12.0" 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | 17 | flutter: 18 | plugin: 19 | platforms: 20 | android: 21 | package: rekab.app.background_locator 22 | pluginClass: BackgroundLocatorPlugin 23 | ios: 24 | pluginClass: BackgroundLocatorPlugin 25 | -------------------------------------------------------------------------------- /.github/workflows/build_pr.yml: -------------------------------------------------------------------------------- 1 | name: build_pr 2 | 3 | on: 4 | pull_request: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: macos-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-java@v1 15 | with: 16 | java-version: '12.x' 17 | - uses: subosito/flutter-action@v1 18 | with: 19 | channel: 'stable' 20 | 21 | - name: Install dependencies 22 | run: flutter pub get 23 | - name: Build debug akp 24 | run: | 25 | cd example 26 | flutter build apk --debug 27 | - name: Build debug ios app 28 | run: | 29 | cd example 30 | flutter build ios --debug --no-codesign 31 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.4.31' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.3' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | tags: 7 | - '*' 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-java@v1 17 | with: 18 | java-version: '12.x' 19 | - uses: subosito/flutter-action@v1 20 | with: 21 | channel: 'stable' 22 | 23 | - name: Install dependencies 24 | run: flutter pub get 25 | - name: Build debug akp 26 | run: | 27 | cd example 28 | flutter build apk --debug 29 | - name: Build debug ios app 30 | run: | 31 | cd example 32 | flutter build ios --debug --no-codesign 33 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 14 5 | # Label requiring a response 6 | responseRequiredLabel: more-information-needed 7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 8 | closeComment: > 9 | This issue has been automatically closed because there has been no response 10 | to our request for more information from the original author. With only the 11 | information that is currently in the issue, we don't have enough information 12 | to take action. Please reach out if you have or find the answers we need so 13 | that we can investigate further. 14 | -------------------------------------------------------------------------------- /ios/Classes/Preferences/PreferencesManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesManager.h 3 | // background_locator 4 | // 5 | // Created by Mehdi Sohrabi on 6/28/20. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface PreferencesManager : NSObject 13 | 14 | + (int64_t)getCallbackDispatcherHandle; 15 | + (void)setCallbackDispatcherHandle:(int64_t)handle; 16 | + (int64_t)getCallbackHandle:(NSString *)key; 17 | + (void)setCallbackHandle:(int64_t)handle key:(NSString *)key; 18 | + (void)saveDistanceFilter:(double) distance; 19 | + (double)getDistanceFilter; 20 | + (void)setObservingRegion:(BOOL) observing; 21 | + (BOOL)isObservingRegion; 22 | + (void)setServiceRunning:(BOOL) running; 23 | + (BOOL)isServiceRunning; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /ios/background_locator.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'background_locator' 6 | s.version = '0.0.1' 7 | s.summary = 'A Flutter plugin for getting location updates even when the app is killed.' 8 | s.description = <<-DESC 9 | A new Flutter plugin. 10 | DESC 11 | s.homepage = 'https://github.com/rekab-app/background_locator' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'REKAB' => 'mehdok@gmail.com' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | s.dependency 'Flutter' 18 | 19 | s.ios.deployment_target = '8.0' 20 | end 21 | 22 | -------------------------------------------------------------------------------- /lib/settings/locator_settings.dart: -------------------------------------------------------------------------------- 1 | class LocationAccuracy { 2 | const LocationAccuracy._internal(this.value); 3 | 4 | final int value; 5 | 6 | static const POWERSAVE = LocationAccuracy._internal(0); 7 | static const LOW = LocationAccuracy._internal(1); 8 | static const BALANCED = LocationAccuracy._internal(2); 9 | static const HIGH = LocationAccuracy._internal(3); 10 | static const NAVIGATION = LocationAccuracy._internal(4); 11 | } 12 | 13 | class LocatorSettings { 14 | final LocationAccuracy accuracy; 15 | final double distanceFilter; 16 | 17 | /// [accuracy] The accuracy of location, Default is max accuracy NAVIGATION. 18 | /// 19 | /// [distanceFilter] distance in meter to trigger location update, Default is 0 meter. 20 | const LocatorSettings({required this.accuracy, required this.distanceFilter}); 21 | } 22 | -------------------------------------------------------------------------------- /ios/Classes/pluggables/DisposePluggable.m: -------------------------------------------------------------------------------- 1 | // 2 | // DisposePluggable.m 3 | // background_locator 4 | // 5 | // Created by Mehdok on 6/7/21. 6 | // 7 | 8 | #import "DisposePluggable.h" 9 | #import "PreferencesManager.h" 10 | #import "Globals.h" 11 | #import "BackgroundLocatorPlugin.h" 12 | 13 | @implementation DisposePluggable 14 | 15 | - (void)onServiceDispose { 16 | NSDictionary *map = @{ 17 | kArgDisposeCallback : @([PreferencesManager getCallbackHandle:kDisposeCallbackKey]) 18 | }; 19 | [[BackgroundLocatorPlugin getInstance] invokeMethod:kBCMDispose arguments:map]; 20 | } 21 | 22 | - (void)onServiceStart:(NSDictionary *)initialDataDictionary { 23 | // nop 24 | } 25 | 26 | - (void)setCallback:(int64_t)callbackHandle { 27 | [PreferencesManager setCallbackHandle:callbackHandle key:kDisposeCallbackKey]; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /example/lib/file_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:path_provider/path_provider.dart'; 4 | 5 | class FileManager { 6 | static Future writeToLogFile(String log) async { 7 | final file = await _getTempLogFile(); 8 | await file.writeAsString(log, mode: FileMode.append); 9 | } 10 | 11 | static Future readLogFile() async { 12 | final file = await _getTempLogFile(); 13 | return file.readAsString(); 14 | } 15 | 16 | static Future _getTempLogFile() async { 17 | final directory = await getTemporaryDirectory(); 18 | final file = File('${directory.path}/log.txt'); 19 | if (!await file.exists()) { 20 | await file.writeAsString(''); 21 | } 22 | return file; 23 | } 24 | 25 | static Future clearLogFile() async { 26 | final file = await _getTempLogFile(); 27 | await file.writeAsString(''); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:background_locator_example/main.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_test/flutter_test.dart'; 11 | 12 | void main() { 13 | testWidgets('Verify Platform version', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(MyApp()); 16 | 17 | // Verify that platform version is retrieved. 18 | expect( 19 | find.byWidgetPredicate( 20 | (Widget widget) => 21 | widget is Text && widget.data.startsWith('Running on:'), 22 | ), 23 | findsOneWidget, 24 | ); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/rekab/app/background_locator_example/Application.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator_example 2 | 3 | import io.flutter.app.FlutterApplication 4 | import io.flutter.plugin.common.PluginRegistry 5 | import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback 6 | import io.flutter.plugins.pathprovider.PathProviderPlugin 7 | import io.flutter.view.FlutterMain 8 | import rekab.app.background_locator.IsolateHolderService 9 | 10 | //class Application : FlutterApplication(), PluginRegistrantCallback { 11 | // override fun onCreate() { 12 | // super.onCreate() 13 | // IsolateHolderService.setPluginRegistrant(this) 14 | // FlutterMain.startInitialization(this) 15 | // } 16 | // 17 | // override fun registerWith(registry: PluginRegistry?) { 18 | // if (!registry!!.hasPlugin("io.flutter.plugins.pathprovider")) { 19 | // PathProviderPlugin.registerWith(registry.registrarFor("io.flutter.plugins.pathprovider")) 20 | // } 21 | // } 22 | //} 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 REKAB 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /example/lib/location_callback_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:background_locator/location_dto.dart'; 4 | 5 | import 'location_service_repository.dart'; 6 | 7 | class LocationCallbackHandler { 8 | static Future initCallback(Map params) async { 9 | LocationServiceRepository myLocationCallbackRepository = 10 | LocationServiceRepository(); 11 | await myLocationCallbackRepository.init(params); 12 | } 13 | 14 | static Future disposeCallback() async { 15 | LocationServiceRepository myLocationCallbackRepository = 16 | LocationServiceRepository(); 17 | await myLocationCallbackRepository.dispose(); 18 | } 19 | 20 | static Future callback(LocationDto locationDto) async { 21 | LocationServiceRepository myLocationCallbackRepository = 22 | LocationServiceRepository(); 23 | await myLocationCallbackRepository.callback(locationDto); 24 | } 25 | 26 | static Future notificationCallback() async { 27 | print('***notificationCallback'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ios/Classes/Helpers/MethodCallHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // MethodCallHelper.h 3 | // background_locator 4 | // 5 | // Created by Mehdi Sohrabi on 6/28/20. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | @protocol MethodCallHelperDelegate 12 | - (void) startLocatorService:(int64_t) callbackDispatcher; 13 | - (void)registerLocator:(int64_t)callback 14 | initCallback:(int64_t)initCallback 15 | initialDataDictionary:(NSDictionary *_Nullable)initialDataDictionary 16 | disposeCallback:(int64_t)disposeCallback 17 | settings:(NSDictionary *_Nonnull)settings; 18 | - (void) removeLocator; 19 | - (BOOL) isServiceRunning; 20 | - (void) setServiceRunning:(BOOL)value; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_BEGIN 25 | 26 | @interface MethodCallHelper : NSObject 27 | 28 | - (void)handleMethodCall:(FlutterMethodCall *)call 29 | result:(FlutterResult)result 30 | delegate:(id )delegate; 31 | 32 | @end 33 | 34 | NS_ASSUME_NONNULL_END 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/settings/ios_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:background_locator/keys.dart'; 2 | 3 | import 'locator_settings.dart'; 4 | 5 | class IOSSettings extends LocatorSettings { 6 | /// [accuracy] The accuracy of location, Default is max accuracy NAVIGATION. 7 | /// 8 | /// [distanceFilter] distance in meter to trigger location update, Default is 0 meter. 9 | /// 10 | /// [showsBackgroundLocationIndicator] The background location usage indicator is a blue bar or a blue pill in the status bar on iOS. Default is false. 11 | 12 | final bool showsBackgroundLocationIndicator; 13 | const IOSSettings({ 14 | LocationAccuracy accuracy = LocationAccuracy.NAVIGATION, 15 | double distanceFilter = 0, 16 | this.showsBackgroundLocationIndicator = false, 17 | }) : super(accuracy: accuracy, distanceFilter: distanceFilter); //minutes 18 | 19 | Map toMap() { 20 | return { 21 | Keys.SETTINGS_ACCURACY: accuracy.value, 22 | Keys.SETTINGS_DISTANCE_FILTER: distanceFilter, 23 | Keys.SETTINGS_IOS_SHOWS_BACKGROUND_LOCATION_INDICATOR: 24 | showsBackgroundLocationIndicator, 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import background_locator 2 | import Flutter 3 | import path_provider_ios 4 | import UIKit 5 | 6 | func registerPlugins(registry: FlutterPluginRegistry) { 7 | GeneratedPluginRegistrant.register(with: registry) 8 | } 9 | 10 | @UIApplicationMain 11 | @objc class AppDelegate: FlutterAppDelegate { 12 | override func application( 13 | _ application: UIApplication, 14 | didFinishLaunchingWithOptions launchOptions: [UIApplication 15 | .LaunchOptionsKey: Any]? 16 | ) -> Bool { 17 | GeneratedPluginRegistrant.register(with: self) 18 | BackgroundLocatorPlugin.setPluginRegistrantCallback(registerPlugins) 19 | 20 | registerOtherPlugins() 21 | 22 | return super 23 | .application(application, 24 | didFinishLaunchingWithOptions: launchOptions) 25 | } 26 | 27 | func registerOtherPlugins() { 28 | if !hasPlugin("io.flutter.plugins.pathprovider") { 29 | FLTPathProviderPlugin 30 | .register(with: registrar(forPlugin: "io.flutter.plugins.pathprovider")!) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'rekab.app.background_locator' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.4.21' 6 | repositories { 7 | google() 8 | jcenter() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:4.1.1' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | compileSdkVersion 30 29 | 30 | sourceSets { 31 | main.java.srcDirs += 'src/main/kotlin' 32 | } 33 | defaultConfig { 34 | minSdkVersion 16 35 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 36 | } 37 | lintOptions { 38 | disable 'InvalidPackage' 39 | } 40 | } 41 | 42 | dependencies { 43 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 44 | implementation "com.google.android.gms:play-services-location:18.0.0" 45 | implementation 'com.google.code.gson:gson:2.8.6' 46 | } 47 | -------------------------------------------------------------------------------- /ios/Classes/pluggables/InitPluggable.m: -------------------------------------------------------------------------------- 1 | // 2 | // InitPluggable.m 3 | // background_locator 4 | // 5 | // Created by Mehdok on 6/7/21. 6 | // 7 | 8 | #import "InitPluggable.h" 9 | #import "PreferencesManager.h" 10 | #import "Globals.h" 11 | #import "BackgroundLocatorPlugin.h" 12 | 13 | @implementation InitPluggable { 14 | BOOL isInitCallbackCalled; 15 | } 16 | 17 | - (instancetype)init { 18 | self = [super init]; 19 | if (self) { 20 | isInitCallbackCalled = NO; 21 | } 22 | return self; 23 | } 24 | 25 | - (void)onServiceDispose { 26 | isInitCallbackCalled = NO; 27 | } 28 | 29 | - (void)onServiceStart:(NSDictionary*) initialDataDictionary { 30 | if (!isInitCallbackCalled) { 31 | NSDictionary *map = @{ 32 | kArgInitCallback : @([PreferencesManager getCallbackHandle:kInitCallbackKey]), 33 | kArgInitDataCallback: initialDataDictionary 34 | }; 35 | [[BackgroundLocatorPlugin getInstance] invokeMethod:kBCMInit arguments:map]; 36 | } 37 | isInitCallbackCalled = YES; 38 | } 39 | 40 | - (void)setCallback:(int64_t)callbackHandle { 41 | [PreferencesManager setCallbackHandle:callbackHandle key:kInitCallbackKey]; 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/pluggables/DisposePluggable.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator.pluggables 2 | 3 | import android.content.Context 4 | import android.os.Handler 5 | import io.flutter.plugin.common.MethodChannel 6 | import rekab.app.background_locator.IsolateHolderService 7 | import rekab.app.background_locator.Keys 8 | import rekab.app.background_locator.PreferencesManager 9 | 10 | class DisposePluggable : Pluggable { 11 | override fun setCallback(context: Context, callbackHandle: Long) { 12 | PreferencesManager.setCallbackHandle(context, Keys.DISPOSE_CALLBACK_HANDLE_KEY, callbackHandle) 13 | } 14 | 15 | override fun onServiceDispose(context: Context) { 16 | (PreferencesManager.getCallbackHandle(context, Keys.DISPOSE_CALLBACK_HANDLE_KEY))?.let { disposeCallback -> 17 | val backgroundChannel = MethodChannel(IsolateHolderService.backgroundEngine?.dartExecutor?.binaryMessenger, 18 | Keys.BACKGROUND_CHANNEL_ID) 19 | Handler(context.mainLooper) 20 | .post { 21 | backgroundChannel.invokeMethod(Keys.BCM_DISPOSE, 22 | hashMapOf(Keys.ARG_DISPOSE_CALLBACK to disposeCallback)) 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /ios/Classes/Utils/Util.m: -------------------------------------------------------------------------------- 1 | // 2 | // Util.m 3 | // background_locator 4 | // 5 | // Created by Mehdi Sohrabi on 6/28/20. 6 | // 7 | 8 | #import "Util.h" 9 | #import "GLobals.h" 10 | 11 | @implementation Util 12 | 13 | + (CLLocationAccuracy) getAccuracy:(long)key { 14 | switch (key) { 15 | case 0: 16 | return kCLLocationAccuracyKilometer; 17 | case 1: 18 | return kCLLocationAccuracyHundredMeters; 19 | case 2: 20 | return kCLLocationAccuracyNearestTenMeters; 21 | case 3: 22 | return kCLLocationAccuracyBest; 23 | case 4: 24 | return kCLLocationAccuracyBestForNavigation; 25 | default: 26 | return kCLLocationAccuracyBestForNavigation; 27 | } 28 | } 29 | 30 | + (NSDictionary *)getLocationMap:(CLLocation *)location { 31 | NSTimeInterval timeInSeconds = [location.timestamp timeIntervalSince1970]; 32 | return @{ 33 | kArgLatitude: @(location.coordinate.latitude), 34 | kArgLongitude: @(location.coordinate.longitude), 35 | kArgAccuracy: @(location.horizontalAccuracy), 36 | kArgAltitude: @(location.altitude), 37 | kArgSpeed: @(location.speed), 38 | kArgSpeedAccuracy: @(0.0), 39 | kArgHeading: @(location.course), 40 | kArgTime: @(((double) timeInSeconds) * 1000.0) // in milliseconds since the epoch 41 | }; 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/provider/GoogleLocationProviderClient.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator.provider 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import com.google.android.gms.location.* 6 | 7 | class GoogleLocationProviderClient(context: Context, override var listener: LocationUpdateListener?) : BLLocationProvider { 8 | private val client: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) 9 | private val locationCallback = LocationListener(listener) 10 | 11 | override fun removeLocationUpdates() { 12 | client.removeLocationUpdates(locationCallback) 13 | } 14 | 15 | @SuppressLint("MissingPermission") 16 | override fun requestLocationUpdates(request: LocationRequestOptions) { 17 | client.requestLocationUpdates(getLocationRequest(request), locationCallback, null) 18 | } 19 | 20 | private fun getLocationRequest(request: LocationRequestOptions): LocationRequest { 21 | val locationRequest = LocationRequest.create() 22 | 23 | locationRequest.interval = request.interval 24 | locationRequest.fastestInterval = request.interval 25 | locationRequest.maxWaitTime = request.interval 26 | locationRequest.priority = request.accuracy 27 | locationRequest.smallestDisplacement = request.distanceFilter 28 | 29 | return locationRequest 30 | } 31 | } 32 | 33 | private class LocationListener(val listener: LocationUpdateListener?) : LocationCallback() { 34 | override fun onLocationResult(location: LocationResult?) { 35 | listener?.onLocationUpdated(LocationParserUtil.getLocationMapFromLocation(location)) 36 | } 37 | } -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | lib/main_widget_test.dart 31 | coverage 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/ServiceDefinitions.json 65 | **/ios/Runner/GeneratedPluginRegistrant.* 66 | **/ios/.symlinks 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | ios/Frameworks/ 75 | .flutter-plugins-dependencies 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | .vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | build/ 33 | 34 | # Android related 35 | **/android/**/gradle-wrapper.jar 36 | **/android/.gradle 37 | **/android/captures/ 38 | **/android/gradlew 39 | **/android/gradlew.bat 40 | **/android/local.properties 41 | **/android/**/GeneratedPluginRegistrant.java 42 | 43 | # iOS/XCode related 44 | **/ios/**/*.mode1v3 45 | **/ios/**/*.mode2v3 46 | **/ios/**/*.moved-aside 47 | **/ios/**/*.pbxuser 48 | **/ios/**/*.perspectivev3 49 | **/ios/**/*sync/ 50 | **/ios/**/.sconsign.dblite 51 | **/ios/**/.tags* 52 | **/ios/**/.vagrant/ 53 | **/ios/**/DerivedData/ 54 | **/ios/**/Icon? 55 | **/ios/**/Pods/ 56 | **/ios/**/.symlinks/ 57 | **/ios/**/profile 58 | **/ios/**/xcuserdata 59 | **/ios/.generated/ 60 | **/ios/Flutter/App.framework 61 | **/ios/Flutter/Flutter.framework 62 | **/ios/Flutter/Flutter.podspec 63 | **/ios/Flutter/Generated.xcconfig 64 | **/ios/Flutter/ephemeral 65 | **/ios/Flutter/app.flx 66 | **/ios/Flutter/app.zip 67 | **/ios/Flutter/flutter_assets/ 68 | **/ios/Flutter/flutter_export_environment.sh 69 | **/ios/ServiceDefinitions.json 70 | **/ios/Runner/GeneratedPluginRegistrant.* 71 | 72 | # Exceptions to above rules. 73 | !**/ios/**/default.mode1v3 74 | !**/ios/**/default.mode2v3 75 | !**/ios/**/default.pbxuser 76 | !**/ios/**/default.perspectivev3 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # background_locator ![](https://github.com/rekab-app/background_locator/workflows/build/badge.svg) [![pub package](https://img.shields.io/pub/v/background_locator.svg)](https://pub.dartlang.org/packages/background_locator) ![](https://img.shields.io/github/contributors/rekab-app/background_locator) ![](https://img.shields.io/github/license/rekab-app/background_locator) 3 | 4 | A Flutter plugin for getting location updates even when the app is killed. 5 | 6 | ![demo](https://raw.githubusercontent.com/RomanJos/background_locator/master/demo.gif) 7 | 8 | Refer to [wiki](https://github.com/rekab-app/background_locator/wiki) page for install and setup instruction or jump to specific subject with below links: 9 | 10 | * [Installation](https://github.com/rekab-app/background_locator/wiki/Installation) 11 | * [Setup](https://github.com/rekab-app/background_locator/wiki/Setup) 12 | * [How to use](https://github.com/rekab-app/background_locator/wiki/How-to-use) 13 | * [Use other plugins in callback](https://github.com/rekab-app/background_locator/wiki/Use-other-plugins-in-callback) 14 | * [Stop on app terminate](https://github.com/rekab-app/background_locator/wiki/Stop-on-app-terminate) 15 | * [LocationSettings options](https://github.com/rekab-app/background_locator/wiki/LocationSettings-options) 16 | * [Restart service on device reboot (Android only)](https://github.com/rekab-app/background_locator/wiki/Restart-service-on-device-reboot) 17 | 18 | ## License 19 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 20 | 21 | ## Contributor 22 | Thanks to all who contributed on this plugin to fix bugs and adding new feature, including: 23 | * [Gerardo Ibarra](https://github.com/gpibarra) 24 | * [RomanJos](https://github.com/RomanJos) 25 | * [Marcelo Henrique Neppel](https://github.com/marceloneppel) 26 | -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/pluggables/InitPluggable.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator.pluggables 2 | 3 | import android.content.Context 4 | import android.os.Handler 5 | import io.flutter.plugin.common.MethodChannel 6 | import rekab.app.background_locator.IsolateHolderService 7 | import rekab.app.background_locator.Keys 8 | import rekab.app.background_locator.PreferencesManager 9 | 10 | class InitPluggable : Pluggable { 11 | private var isInitCallbackCalled = false 12 | 13 | override fun setCallback(context: Context, callbackHandle: Long) { 14 | PreferencesManager.setCallbackHandle(context, Keys.INIT_CALLBACK_HANDLE_KEY, callbackHandle) 15 | 16 | } 17 | 18 | override fun onServiceStart(context: Context) { 19 | if (!isInitCallbackCalled) { 20 | (PreferencesManager.getCallbackHandle(context, Keys.INIT_CALLBACK_HANDLE_KEY))?.let { initCallback -> 21 | val initialDataMap = PreferencesManager.getDataCallback(context, Keys.INIT_DATA_CALLBACK_KEY) 22 | val backgroundChannel = MethodChannel(IsolateHolderService.backgroundEngine?.dartExecutor?.binaryMessenger, 23 | Keys.BACKGROUND_CHANNEL_ID) 24 | Handler(context.mainLooper) 25 | .post { 26 | backgroundChannel.invokeMethod(Keys.BCM_INIT, 27 | hashMapOf(Keys.ARG_INIT_CALLBACK to initCallback, Keys.ARG_INIT_DATA_CALLBACK to initialDataMap)) 28 | } 29 | } 30 | isInitCallbackCalled = true 31 | } 32 | } 33 | 34 | override fun onServiceDispose(context: Context) { 35 | isInitCallbackCalled = false 36 | } 37 | 38 | fun setInitData(context: Context, data: Map<*, *>) { 39 | PreferencesManager.setDataCallback(context, Keys.INIT_DATA_CALLBACK_KEY, data) 40 | } 41 | } -------------------------------------------------------------------------------- /lib/location_dto.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | 3 | import 'keys.dart'; 4 | 5 | class LocationDto { 6 | final double latitude; 7 | final double longitude; 8 | final double accuracy; 9 | final double altitude; 10 | final double speed; 11 | final double speedAccuracy; 12 | final double heading; 13 | final double time; 14 | final bool isMocked; 15 | final String provider; 16 | 17 | LocationDto._( 18 | this.latitude, 19 | this.longitude, 20 | this.accuracy, 21 | this.altitude, 22 | this.speed, 23 | this.speedAccuracy, 24 | this.heading, 25 | this.time, 26 | this.isMocked, 27 | this.provider, 28 | ); 29 | 30 | factory LocationDto.fromJson(Map json) { 31 | bool isLocationMocked = 32 | Platform.isAndroid ? json[Keys.ARG_IS_MOCKED] : false; 33 | return LocationDto._( 34 | json[Keys.ARG_LATITUDE], 35 | json[Keys.ARG_LONGITUDE], 36 | json[Keys.ARG_ACCURACY], 37 | json[Keys.ARG_ALTITUDE], 38 | json[Keys.ARG_SPEED], 39 | json[Keys.ARG_SPEED_ACCURACY], 40 | json[Keys.ARG_HEADING], 41 | json[Keys.ARG_TIME], 42 | isLocationMocked, 43 | json[Keys.ARG_PROVIDER], 44 | ); 45 | } 46 | 47 | Map toJson() { 48 | return { 49 | Keys.ARG_LATITUDE: this.latitude, 50 | Keys.ARG_LONGITUDE: this.longitude, 51 | Keys.ARG_ACCURACY: this.accuracy, 52 | Keys.ARG_ALTITUDE: this.altitude, 53 | Keys.ARG_SPEED: this.speed, 54 | Keys.ARG_SPEED_ACCURACY: this.speedAccuracy, 55 | Keys.ARG_HEADING: this.heading, 56 | Keys.ARG_TIME: this.time, 57 | Keys.ARG_IS_MOCKED: this.isMocked, 58 | Keys.ARG_PROVIDER: this.provider, 59 | }; 60 | } 61 | 62 | @override 63 | String toString() { 64 | return 'LocationDto{latitude: $latitude, longitude: $longitude, accuracy: $accuracy, altitude: $altitude, speed: $speed, speedAccuracy: $speedAccuracy, heading: $heading, time: $time, isMocked: $isMocked, provider: $provider}'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ios/Classes/Preferences/PreferencesManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesManager.m 3 | // background_locator 4 | // 5 | // Created by Mehdi Sohrabi on 6/28/20. 6 | // 7 | 8 | #import "PreferencesManager.h" 9 | #import "Globals.h" 10 | 11 | @implementation PreferencesManager 12 | 13 | + (int64_t)getCallbackDispatcherHandle { 14 | id handle = [[NSUserDefaults standardUserDefaults] 15 | objectForKey: kCallbackDispatcherKey]; 16 | if (handle == nil) { 17 | return 0; 18 | } 19 | return [handle longLongValue]; 20 | } 21 | 22 | + (void)setCallbackDispatcherHandle:(int64_t)handle { 23 | [[NSUserDefaults standardUserDefaults] 24 | setObject:[NSNumber numberWithLongLong:handle] 25 | forKey:kCallbackDispatcherKey]; 26 | } 27 | 28 | + (int64_t)getCallbackHandle:(NSString *)key { 29 | id handle = [[NSUserDefaults standardUserDefaults] 30 | objectForKey: key]; 31 | if (handle == nil) { 32 | return 0; 33 | } 34 | return [handle longLongValue]; 35 | } 36 | 37 | + (void)setCallbackHandle:(int64_t)handle key:(NSString *)key { 38 | [[NSUserDefaults standardUserDefaults] 39 | setObject:[NSNumber numberWithLongLong:handle] 40 | forKey: key]; 41 | } 42 | 43 | + (void)saveDistanceFilter:(double)distance { 44 | [[NSUserDefaults standardUserDefaults] setDouble:distance forKey:kDistanceFilterKey]; 45 | } 46 | 47 | + (double)getDistanceFilter { 48 | return [[NSUserDefaults standardUserDefaults] doubleForKey:kDistanceFilterKey]; 49 | } 50 | 51 | + (void)setObservingRegion:(BOOL)observing { 52 | [[NSUserDefaults standardUserDefaults] setBool:observing forKey:kPrefObservingRegion]; 53 | } 54 | 55 | + (BOOL)isObservingRegion { 56 | return [[NSUserDefaults standardUserDefaults] boolForKey:kPrefObservingRegion]; 57 | } 58 | 59 | + (void)setServiceRunning:(BOOL)running { 60 | [[NSUserDefaults standardUserDefaults] setBool:running forKey:kPrefServiceRunning]; 61 | } 62 | 63 | + (BOOL)isServiceRunning { 64 | return [[NSUserDefaults standardUserDefaults] boolForKey:kPrefServiceRunning]; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSLocationAlwaysAndWhenInUseUsageDescription 26 | Location service needed. 27 | NSLocationAlwaysUsageDescription 28 | Location service needed. 29 | NSLocationWhenInUseUsageDescription 30 | Location service needed. 31 | UIBackgroundModes 32 | 33 | location 34 | 35 | UILaunchStoryboardName 36 | LaunchScreen 37 | UIMainStoryboardFile 38 | Main 39 | UISupportedInterfaceOrientations 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | UISupportedInterfaceOrientations~ipad 46 | 47 | UIInterfaceOrientationPortrait 48 | UIInterfaceOrientationPortraitUpsideDown 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UIViewControllerBasedStatusBarAppearance 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /lib/callback_dispatcher.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | 6 | import 'keys.dart'; 7 | import 'location_dto.dart'; 8 | 9 | @pragma('vm:entry-point') 10 | void callbackDispatcher() { 11 | const MethodChannel _backgroundChannel = 12 | MethodChannel(Keys.BACKGROUND_CHANNEL_ID); 13 | WidgetsFlutterBinding.ensureInitialized(); 14 | 15 | _backgroundChannel.setMethodCallHandler((MethodCall call) async { 16 | if (Keys.BCM_SEND_LOCATION == call.method) { 17 | final Map args = call.arguments; 18 | final Function callback = PluginUtilities.getCallbackFromHandle( 19 | CallbackHandle.fromRawHandle(args[Keys.ARG_CALLBACK]))!; 20 | final LocationDto location = 21 | LocationDto.fromJson(args[Keys.ARG_LOCATION]); 22 | callback(location); 23 | } else if (Keys.BCM_NOTIFICATION_CLICK == call.method) { 24 | final Map args = call.arguments; 25 | final Function? notificationCallback = 26 | PluginUtilities.getCallbackFromHandle(CallbackHandle.fromRawHandle( 27 | args[Keys.ARG_NOTIFICATION_CALLBACK])); 28 | if (notificationCallback != null) { 29 | notificationCallback(); 30 | } 31 | } else if (Keys.BCM_INIT == call.method) { 32 | final Map args = call.arguments; 33 | final Function? initCallback = PluginUtilities.getCallbackFromHandle( 34 | CallbackHandle.fromRawHandle(args[Keys.ARG_INIT_CALLBACK])); 35 | Map? data = args[Keys.ARG_INIT_DATA_CALLBACK]; 36 | if (initCallback != null) { 37 | initCallback(data); 38 | } 39 | } else if (Keys.BCM_DISPOSE == call.method) { 40 | final Map args = call.arguments; 41 | final Function? disposeCallback = PluginUtilities.getCallbackFromHandle( 42 | CallbackHandle.fromRawHandle(args[Keys.ARG_DISPOSE_CALLBACK])); 43 | if (disposeCallback != null) { 44 | disposeCallback(); 45 | } 46 | } 47 | }); 48 | _backgroundChannel.invokeMethod(Keys.METHOD_SERVICE_INITIALIZED); 49 | } 50 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: background_locator_example 2 | description: Demonstrates how to use the background_locator plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: ">=2.8.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | # The following adds the Cupertino Icons font to your application. 13 | # Use with the CupertinoIcons class for iOS style icons. 14 | cupertino_icons: ^1.0.2 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | background_locator: 21 | path: ../ 22 | 23 | path_provider: ^2.0.8 24 | location_permissions: ^3.0.0+1 25 | 26 | # For information on the generic Dart part of this file, see the 27 | # following page: https://dart.dev/tools/pub/pubspec 28 | 29 | # The following section is specific to Flutter. 30 | flutter: 31 | 32 | # The following line ensures that the Material Icons font is 33 | # included with your application, so that you can use the icons in 34 | # the material Icons class. 35 | uses-material-design: true 36 | 37 | # To add assets to your application, add an assets section, like this: 38 | # assets: 39 | # - images/a_dot_burr.jpeg 40 | # - images/a_dot_ham.jpeg 41 | 42 | # An image asset can refer to one or more resolution-specific "variants", see 43 | # https://flutter.dev/assets-and-images/#resolution-aware. 44 | 45 | # For details regarding adding assets from package dependencies, see 46 | # https://flutter.dev/assets-and-images/#from-packages 47 | 48 | # To add custom fonts to your application, add a fonts section here, 49 | # in this "flutter" section. Each entry in this list should have a 50 | # "family" key with the font family name, and a "fonts" key with a 51 | # list giving the asset and other descriptors for the font. For 52 | # example: 53 | # fonts: 54 | # - family: Schyler 55 | # fonts: 56 | # - asset: fonts/Schyler-Regular.ttf 57 | # - asset: fonts/Schyler-Italic.ttf 58 | # style: italic 59 | # - family: Trajan Pro 60 | # fonts: 61 | # - asset: fonts/TrajanPro.ttf 62 | # - asset: fonts/TrajanPro_Bold.ttf 63 | # weight: 700 64 | # 65 | # For details regarding fonts from package dependencies, 66 | # see https://flutter.dev/custom-fonts/#from-packages 67 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 30 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "rekab.app.background_locator_example" 42 | minSdkVersion 16 43 | targetSdkVersion 30 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.3.0' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 67 | } 68 | -------------------------------------------------------------------------------- /ios/Classes/Helpers/MethodCallHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // MethodCallHelper.m 3 | // background_locator 4 | // 5 | // Created by Mehdi Sohrabi on 6/28/20. 6 | // 7 | 8 | #import "MethodCallHelper.h" 9 | #import "Globals.h" 10 | 11 | @implementation MethodCallHelper 12 | 13 | - (void)handleMethodCall:(FlutterMethodCall *)call 14 | result:(FlutterResult)result 15 | delegate:(id )delegate { 16 | NSDictionary *arguments = call.arguments; 17 | if ([kMethodPluginInitializeService isEqualToString:call.method]) { 18 | int64_t callbackDispatcher = [[arguments objectForKey:kArgCallbackDispatcher] longLongValue]; 19 | [delegate startLocatorService:callbackDispatcher]; 20 | result(@(YES)); 21 | } else if ([kMethodServiceInitialized isEqualToString:call.method]) { 22 | result(nil); 23 | } else if ([kMethodPluginRegisterLocationUpdate isEqualToString:call.method]) { 24 | int64_t callbackHandle = [[arguments objectForKey:kArgCallback] longLongValue]; 25 | int64_t initCallbackHandle = [[arguments objectForKey:kArgInitCallback] longLongValue]; 26 | NSDictionary *initialDataDictionary = [arguments objectForKey:kArgInitDataCallback]; 27 | int64_t disposeCallbackHandle = [[arguments objectForKey:kArgDisposeCallback] longLongValue]; 28 | [delegate setServiceRunning:true]; 29 | [delegate registerLocator:callbackHandle initCallback:initCallbackHandle initialDataDictionary:initialDataDictionary disposeCallback:disposeCallbackHandle settings:arguments]; 30 | result(@(YES)); 31 | } else if ([kMethodPluginUnRegisterLocationUpdate isEqualToString:call.method]) { 32 | [delegate removeLocator]; 33 | [delegate setServiceRunning:false]; 34 | result(@(YES)); 35 | } else if ([kMethodPluginIsRegisteredLocationUpdate isEqualToString:call.method]) { 36 | BOOL val = [delegate isServiceRunning]; 37 | result(@(val)); 38 | }else if ([kMethodPluginIsServiceRunning isEqualToString:call.method]) { 39 | BOOL val = [delegate isServiceRunning]; 40 | result(@(val)); 41 | } else if([kMethodPluginUpdateNotification isEqualToString:call.method]) { 42 | // updating notification's text is just for android 43 | result(nil); 44 | } else { 45 | result(FlutterMethodNotImplemented); 46 | } 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Classes/Globals.h: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.h 3 | // background_locator 4 | // 5 | // Created by Mehdi Sohrabi on 6/3/20. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface Globals : NSObject 13 | 14 | FOUNDATION_EXPORT NSString *const kCallbackDispatcherKey; 15 | FOUNDATION_EXPORT NSString *const kCallbackKey; 16 | FOUNDATION_EXPORT NSString *const kInitCallbackKey; 17 | FOUNDATION_EXPORT NSString *const kInitDataCallbackKey; 18 | FOUNDATION_EXPORT NSString *const kDisposeCallbackKey; 19 | FOUNDATION_EXPORT NSString *const kDistanceFilterKey; 20 | 21 | FOUNDATION_EXPORT NSString *const kChannelId; 22 | FOUNDATION_EXPORT NSString *const kBackgroundChannelId; 23 | 24 | FOUNDATION_EXPORT NSString *const kMethodServiceInitialized; 25 | FOUNDATION_EXPORT NSString *const kMethodPluginInitializeService; 26 | FOUNDATION_EXPORT NSString *const kMethodPluginRegisterLocationUpdate; 27 | FOUNDATION_EXPORT NSString *const kMethodPluginUnRegisterLocationUpdate; 28 | FOUNDATION_EXPORT NSString *const kMethodPluginIsRegisteredLocationUpdate; 29 | FOUNDATION_EXPORT NSString *const kMethodPluginIsServiceRunning; 30 | FOUNDATION_EXPORT NSString *const kMethodPluginUpdateNotification; 31 | 32 | FOUNDATION_EXPORT NSString *const kArgLatitude; 33 | FOUNDATION_EXPORT NSString *const kArgLongitude; 34 | FOUNDATION_EXPORT NSString *const kArgAccuracy; 35 | FOUNDATION_EXPORT NSString *const kArgAltitude; 36 | FOUNDATION_EXPORT NSString *const kArgSpeed; 37 | FOUNDATION_EXPORT NSString *const kArgSpeedAccuracy; 38 | FOUNDATION_EXPORT NSString *const kArgHeading; 39 | FOUNDATION_EXPORT NSString *const kArgTime; 40 | FOUNDATION_EXPORT NSString *const kArgCallback; 41 | FOUNDATION_EXPORT NSString *const kArgInitCallback; 42 | FOUNDATION_EXPORT NSString *const kArgInitDataCallback; 43 | FOUNDATION_EXPORT NSString *const kArgDisposeCallback; 44 | FOUNDATION_EXPORT NSString *const kArgLocation; 45 | FOUNDATION_EXPORT NSString *const kArgSettings; 46 | FOUNDATION_EXPORT NSString *const kArgCallbackDispatcher; 47 | 48 | FOUNDATION_EXPORT NSString *const kSettingsAccuracy; 49 | FOUNDATION_EXPORT NSString *const kSettingsDistanceFilter; 50 | FOUNDATION_EXPORT NSString *const kSettingsShowsBackgroundLocationIndicator; 51 | 52 | FOUNDATION_EXPORT NSString *const kBCMSendLocation; 53 | FOUNDATION_EXPORT NSString *const kBCMInit; 54 | FOUNDATION_EXPORT NSString *const kBCMDispose; 55 | 56 | FOUNDATION_EXPORT NSString *const kPrefObservingRegion; 57 | FOUNDATION_EXPORT NSString *const kPrefServiceRunning; 58 | 59 | @end 60 | 61 | NS_ASSUME_NONNULL_END 62 | -------------------------------------------------------------------------------- /ios/Classes/Globals.m: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.m 3 | // background_locator 4 | // 5 | // Created by Mehdi Sohrabi on 6/3/20. 6 | // 7 | 8 | #import "Globals.h" 9 | 10 | @implementation Globals 11 | 12 | NSString *const kCallbackDispatcherKey = @"callback_dispatcher_handle_key"; 13 | NSString *const kCallbackKey = @"callback_handle_key"; 14 | NSString *const kInitCallbackKey = @"init_callback_handle_key"; 15 | NSString *const kInitDataCallbackKey = @"init_data_callback_key"; 16 | NSString *const kDisposeCallbackKey = @"dispose_callback_handle_key"; 17 | NSString *const kDistanceFilterKey = @"distance_filter_key"; 18 | NSString *const kChannelId = @"app.rekab/locator_plugin"; 19 | NSString *const kBackgroundChannelId = @"app.rekab/locator_plugin_background"; 20 | 21 | NSString *const kMethodServiceInitialized = @"LocatorService.initialized"; 22 | NSString *const kMethodPluginInitializeService = @"LocatorPlugin.initializeService"; 23 | NSString *const kMethodPluginRegisterLocationUpdate = @"LocatorPlugin.registerLocationUpdate"; 24 | NSString *const kMethodPluginUnRegisterLocationUpdate = @"LocatorPlugin.unRegisterLocationUpdate"; 25 | NSString *const kMethodPluginIsRegisteredLocationUpdate = @"LocatorPlugin.isRegisterLocationUpdate"; 26 | NSString *const kMethodPluginIsServiceRunning = @"LocatorPlugin.isServiceRunning"; 27 | NSString *const kMethodPluginUpdateNotification = @"LocatorPlugin.updateNotification"; 28 | 29 | NSString *const kArgLatitude = @"latitude"; 30 | NSString *const kArgLongitude = @"longitude"; 31 | NSString *const kArgAccuracy = @"accuracy"; 32 | NSString *const kArgAltitude = @"altitude"; 33 | NSString *const kArgSpeed = @"speed"; 34 | NSString *const kArgSpeedAccuracy = @"speed_accuracy"; 35 | NSString *const kArgHeading = @"heading"; 36 | NSString *const kArgTime = @"time"; 37 | NSString *const kArgCallback = @"callback"; 38 | NSString *const kArgInitCallback = @"initCallback"; 39 | NSString *const kArgInitDataCallback = @"initDataCallback"; 40 | NSString *const kArgDisposeCallback = @"disposeCallback"; 41 | NSString *const kArgLocation = @"location"; 42 | NSString *const kArgSettings = @"settings"; 43 | NSString *const kArgCallbackDispatcher = @"callbackDispatcher"; 44 | 45 | NSString *const kSettingsAccuracy = @"settings_accuracy"; 46 | NSString *const kSettingsDistanceFilter = @"settings_distanceFilter"; 47 | NSString *const kSettingsShowsBackgroundLocationIndicator = @"settings_ios_showsBackgroundLocationIndicator"; 48 | 49 | NSString *const kBCMSendLocation = @"BCM_SEND_LOCATION"; 50 | NSString *const kBCMInit = @"BCM_INIT"; 51 | NSString *const kBCMDispose = @"BCM_DISPOSE"; 52 | 53 | NSString *const kPrefObservingRegion = @"pref_observingRegion"; 54 | NSString *const kPrefServiceRunning = @"pref_serviceRunning"; 55 | 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/provider/LocationParserUtil.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator.provider 2 | 3 | import android.location.Location 4 | import android.os.Build 5 | import com.google.android.gms.location.LocationResult 6 | import rekab.app.background_locator.Keys 7 | import java.util.HashMap 8 | 9 | class LocationParserUtil { 10 | companion object { 11 | fun getLocationMapFromLocation(location: Location): HashMap { 12 | var speedAccuracy = 0f 13 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 14 | speedAccuracy = location.speedAccuracyMetersPerSecond 15 | } 16 | var isMocked = false 17 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 18 | isMocked = location.isFromMockProvider 19 | } 20 | 21 | return hashMapOf( 22 | Keys.ARG_IS_MOCKED to isMocked, 23 | Keys.ARG_LATITUDE to location.latitude, 24 | Keys.ARG_LONGITUDE to location.longitude, 25 | Keys.ARG_ACCURACY to location.accuracy, 26 | Keys.ARG_ALTITUDE to location.altitude, 27 | Keys.ARG_SPEED to location.speed, 28 | Keys.ARG_SPEED_ACCURACY to speedAccuracy, 29 | Keys.ARG_HEADING to location.bearing, 30 | Keys.ARG_TIME to location.time.toDouble(), 31 | Keys.ARG_PROVIDER to location.provider, 32 | ) 33 | } 34 | 35 | fun getLocationMapFromLocation(location: LocationResult?): HashMap? { 36 | val firstLocation = location?.lastLocation ?: return null 37 | 38 | var speedAccuracy = 0f 39 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 40 | speedAccuracy = firstLocation.speedAccuracyMetersPerSecond 41 | } 42 | var isMocked = false 43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 44 | isMocked = firstLocation.isFromMockProvider 45 | } 46 | 47 | return hashMapOf( 48 | Keys.ARG_IS_MOCKED to isMocked, 49 | Keys.ARG_LATITUDE to firstLocation.latitude, 50 | Keys.ARG_LONGITUDE to firstLocation.longitude, 51 | Keys.ARG_ACCURACY to firstLocation.accuracy, 52 | Keys.ARG_ALTITUDE to firstLocation.altitude, 53 | Keys.ARG_SPEED to firstLocation.speed, 54 | Keys.ARG_SPEED_ACCURACY to speedAccuracy, 55 | Keys.ARG_HEADING to firstLocation.bearing, 56 | Keys.ARG_TIME to firstLocation.time.toDouble()) 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /lib/utils/settings_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:ui'; 3 | 4 | import 'package:background_locator/keys.dart'; 5 | import 'package:background_locator/location_dto.dart'; 6 | import 'package:background_locator/settings/android_settings.dart'; 7 | import 'package:background_locator/settings/ios_settings.dart'; 8 | 9 | class SettingsUtil { 10 | static Map getArgumentsMap( 11 | {required void Function(LocationDto) callback, 12 | void Function(Map)? initCallback, 13 | Map? initDataCallback, 14 | void Function()? disposeCallback, 15 | AndroidSettings androidSettings = const AndroidSettings(), 16 | IOSSettings iosSettings = const IOSSettings()}) { 17 | final args = _getCommonArgumentsMap(callback: callback, 18 | initCallback: initCallback, 19 | initDataCallback: initDataCallback, 20 | disposeCallback: disposeCallback); 21 | 22 | if (Platform.isAndroid) { 23 | args.addAll(_getAndroidArgumentsMap(androidSettings)); 24 | } else if (Platform.isIOS) { 25 | args.addAll(_getIOSArgumentsMap(iosSettings)); 26 | } 27 | 28 | return args; 29 | } 30 | 31 | static Map _getCommonArgumentsMap({ 32 | required void Function(LocationDto) callback, 33 | void Function(Map)? initCallback, 34 | Map? initDataCallback, 35 | void Function()? disposeCallback 36 | }) { 37 | final Map args = { 38 | Keys.ARG_CALLBACK: 39 | PluginUtilities.getCallbackHandle(callback)!.toRawHandle(), 40 | }; 41 | 42 | if (initCallback != null) { 43 | args[Keys.ARG_INIT_CALLBACK] = 44 | PluginUtilities.getCallbackHandle(initCallback)!.toRawHandle(); 45 | } 46 | if (disposeCallback != null) { 47 | args[Keys.ARG_DISPOSE_CALLBACK] = 48 | PluginUtilities.getCallbackHandle(disposeCallback)!.toRawHandle(); 49 | } 50 | if (initDataCallback != null ){ 51 | args[Keys.ARG_INIT_DATA_CALLBACK] = initDataCallback; 52 | 53 | } 54 | 55 | return args; 56 | } 57 | 58 | static Map _getAndroidArgumentsMap( 59 | AndroidSettings androidSettings) { 60 | final Map args = { 61 | Keys.ARG_SETTINGS: androidSettings.toMap() 62 | }; 63 | 64 | if (androidSettings.androidNotificationSettings.notificationTapCallback != 65 | null) { 66 | args[Keys.ARG_NOTIFICATION_CALLBACK] = PluginUtilities.getCallbackHandle( 67 | androidSettings 68 | .androidNotificationSettings.notificationTapCallback!)! 69 | .toRawHandle(); 70 | } 71 | 72 | return args; 73 | } 74 | 75 | static Map _getIOSArgumentsMap(IOSSettings iosSettings) { 76 | return iosSettings.toMap(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/background_locator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:ui'; 3 | 4 | import 'package:background_locator/settings/android_settings.dart'; 5 | import 'package:background_locator/settings/ios_settings.dart'; 6 | import 'package:background_locator/utils/settings_util.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:flutter/widgets.dart'; 9 | 10 | import 'auto_stop_handler.dart'; 11 | import 'callback_dispatcher.dart'; 12 | import 'keys.dart'; 13 | import 'location_dto.dart'; 14 | 15 | class BackgroundLocator { 16 | static const MethodChannel _channel = const MethodChannel(Keys.CHANNEL_ID); 17 | 18 | static Future initialize() async { 19 | final CallbackHandle callback = 20 | PluginUtilities.getCallbackHandle(callbackDispatcher)!; 21 | await _channel.invokeMethod(Keys.METHOD_PLUGIN_INITIALIZE_SERVICE, 22 | {Keys.ARG_CALLBACK_DISPATCHER: callback.toRawHandle()}); 23 | } 24 | 25 | static Future registerLocationUpdate( 26 | void Function(LocationDto) callback, 27 | {void Function(Map)? initCallback, 28 | Map initDataCallback = const {}, 29 | void Function()? disposeCallback, 30 | bool autoStop = false, 31 | AndroidSettings androidSettings = const AndroidSettings(), 32 | IOSSettings iosSettings = const IOSSettings()}) async { 33 | if (autoStop) { 34 | WidgetsBinding.instance!.addObserver(AutoStopHandler()); 35 | } 36 | 37 | final args = SettingsUtil.getArgumentsMap( 38 | callback: callback, 39 | initCallback: initCallback, 40 | initDataCallback: initDataCallback, 41 | disposeCallback: disposeCallback, 42 | androidSettings: androidSettings, 43 | iosSettings: iosSettings); 44 | 45 | await _channel.invokeMethod( 46 | Keys.METHOD_PLUGIN_REGISTER_LOCATION_UPDATE, args); 47 | } 48 | 49 | static Future unRegisterLocationUpdate() async { 50 | await _channel.invokeMethod(Keys.METHOD_PLUGIN_UN_REGISTER_LOCATION_UPDATE); 51 | } 52 | 53 | static Future isRegisterLocationUpdate() async { 54 | return (await _channel 55 | .invokeMethod(Keys.METHOD_PLUGIN_IS_REGISTER_LOCATION_UPDATE))!; 56 | } 57 | 58 | static Future isServiceRunning() async { 59 | return (await _channel 60 | .invokeMethod(Keys.METHOD_PLUGIN_IS_SERVICE_RUNNING))!; 61 | } 62 | 63 | static Future updateNotificationText( 64 | {String? title, String? msg, String? bigMsg}) async { 65 | final Map arg = {}; 66 | 67 | if (title != null) { 68 | arg[Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE] = title; 69 | } 70 | 71 | if (msg != null) { 72 | arg[Keys.SETTINGS_ANDROID_NOTIFICATION_MSG] = msg; 73 | } 74 | 75 | if (bigMsg != null) { 76 | arg[Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG] = bigMsg; 77 | } 78 | 79 | await _channel.invokeMethod(Keys.METHOD_PLUGIN_UPDATE_NOTIFICATION, arg); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /example/lib/location_service_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:isolate'; 3 | import 'dart:math'; 4 | import 'dart:ui'; 5 | 6 | import 'package:background_locator/location_dto.dart'; 7 | 8 | import 'file_manager.dart'; 9 | 10 | class LocationServiceRepository { 11 | static LocationServiceRepository _instance = LocationServiceRepository._(); 12 | 13 | LocationServiceRepository._(); 14 | 15 | factory LocationServiceRepository() { 16 | return _instance; 17 | } 18 | 19 | static const String isolateName = 'LocatorIsolate'; 20 | 21 | int _count = -1; 22 | 23 | Future init(Map params) async { 24 | //TODO change logs 25 | print("***********Init callback handler"); 26 | if (params.containsKey('countInit')) { 27 | dynamic tmpCount = params['countInit']; 28 | if (tmpCount is double) { 29 | _count = tmpCount.toInt(); 30 | } else if (tmpCount is String) { 31 | _count = int.parse(tmpCount); 32 | } else if (tmpCount is int) { 33 | _count = tmpCount; 34 | } else { 35 | _count = -2; 36 | } 37 | } else { 38 | _count = 0; 39 | } 40 | print("$_count"); 41 | await setLogLabel("start"); 42 | final SendPort send = IsolateNameServer.lookupPortByName(isolateName); 43 | send?.send(null); 44 | } 45 | 46 | Future dispose() async { 47 | print("***********Dispose callback handler"); 48 | print("$_count"); 49 | await setLogLabel("end"); 50 | final SendPort send = IsolateNameServer.lookupPortByName(isolateName); 51 | send?.send(null); 52 | } 53 | 54 | Future callback(LocationDto locationDto) async { 55 | print('$_count location in dart: ${locationDto.toString()}'); 56 | await setLogPosition(_count, locationDto); 57 | final SendPort send = IsolateNameServer.lookupPortByName(isolateName); 58 | send?.send(locationDto); 59 | _count++; 60 | 61 | } 62 | 63 | static Future setLogLabel(String label) async { 64 | final date = DateTime.now(); 65 | await FileManager.writeToLogFile( 66 | '------------\n$label: ${formatDateLog(date)}\n------------\n'); 67 | } 68 | 69 | static Future setLogPosition(int count, LocationDto data) async { 70 | final date = DateTime.now(); 71 | await FileManager.writeToLogFile( 72 | '$count : ${formatDateLog(date)} --> ${formatLog(data)} --- isMocked: ${data.isMocked}\n'); 73 | } 74 | 75 | static double dp(double val, int places) { 76 | double mod = pow(10.0, places); 77 | return ((val * mod).round().toDouble() / mod); 78 | } 79 | 80 | static String formatDateLog(DateTime date) { 81 | return date.hour.toString() + 82 | ":" + 83 | date.minute.toString() + 84 | ":" + 85 | date.second.toString(); 86 | } 87 | 88 | static String formatLog(LocationDto locationDto) { 89 | return dp(locationDto.latitude, 4).toString() + 90 | " " + 91 | dp(locationDto.longitude, 4).toString(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 24 | 32 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/IsolateHolderExtension.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import com.google.android.gms.location.LocationRequest 6 | import io.flutter.FlutterInjector 7 | import io.flutter.embedding.engine.FlutterEngine 8 | import io.flutter.embedding.engine.dart.DartExecutor 9 | import io.flutter.plugin.common.MethodChannel 10 | import io.flutter.view.FlutterCallbackInformation 11 | import rekab.app.background_locator.provider.LocationRequestOptions 12 | import java.util.concurrent.atomic.AtomicBoolean 13 | 14 | internal fun IsolateHolderService.startLocatorService(context: Context) { 15 | 16 | val serviceStarted = AtomicBoolean(IsolateHolderService.isServiceRunning) 17 | // start synchronized block to prevent multiple service instant 18 | synchronized(serviceStarted) { 19 | this.context = context 20 | if (IsolateHolderService.backgroundEngine == null) { 21 | 22 | val callbackHandle = context.getSharedPreferences( 23 | Keys.SHARED_PREFERENCES_KEY, 24 | Context.MODE_PRIVATE) 25 | .getLong(Keys.CALLBACK_DISPATCHER_HANDLE_KEY, 0) 26 | val callbackInfo = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle) 27 | 28 | // We need flutter engine to handle callback, so if it is not available we have to create a 29 | // Flutter engine without any view 30 | IsolateHolderService.backgroundEngine = FlutterEngine(context) 31 | 32 | val args = DartExecutor.DartCallback( 33 | context.assets, 34 | FlutterInjector.instance().flutterLoader().findAppBundlePath(), 35 | callbackInfo 36 | ) 37 | IsolateHolderService.backgroundEngine?.dartExecutor?.executeDartCallback(args) 38 | } 39 | } 40 | 41 | backgroundChannel = 42 | MethodChannel(IsolateHolderService.backgroundEngine?.dartExecutor?.binaryMessenger, 43 | Keys.BACKGROUND_CHANNEL_ID) 44 | backgroundChannel.setMethodCallHandler(this) 45 | } 46 | 47 | fun getLocationRequest(intent: Intent): LocationRequestOptions { 48 | val interval: Long = (intent.getIntExtra(Keys.SETTINGS_INTERVAL, 10) * 1000).toLong() 49 | val accuracyKey = intent.getIntExtra(Keys.SETTINGS_ACCURACY, 4) 50 | val accuracy = getAccuracy(accuracyKey) 51 | val distanceFilter = intent.getDoubleExtra(Keys.SETTINGS_DISTANCE_FILTER, 0.0) 52 | 53 | return LocationRequestOptions(interval, accuracy, distanceFilter.toFloat()) 54 | } 55 | 56 | fun getAccuracy(key: Int): Int { 57 | return when (key) { 58 | 0 -> LocationRequest.PRIORITY_NO_POWER 59 | 1 -> LocationRequest.PRIORITY_LOW_POWER 60 | 2 -> LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY 61 | 3 -> LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY 62 | 4 -> LocationRequest.PRIORITY_HIGH_ACCURACY 63 | else -> LocationRequest.PRIORITY_HIGH_ACCURACY 64 | } 65 | } -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.6.12 2 | * Fixes onStatusChanged crash; 3 | * Fixed issue #94; 4 | * Fix importing path_provider in ios example; 5 | * Fix issue #266; 6 | * Fix Android 12 location permission handling; 7 | * Add network location provider even gps location data not update; 8 | 9 | ## 1.6.6 10 | * Fix invoking method on flutter channel when engine is not ready; (#254) 11 | 12 | ## 1.6.5 13 | * Fix returning result for unRegisterPlugin method on Android; (#262) 14 | 15 | ## 1.6.4 16 | * Fix triggering location update when plugin is stopped; (#258) 17 | * Fix saving service start stop status; (#259) 18 | 19 | ## 1.6.3 20 | * Bug fixes; 21 | 22 | ## 1.6.2+1-beta 23 | * Bring back init and dispose callback; 24 | 25 | ## 1.6.1+1-beta 26 | * Fix crash on Android location client causing by change in status of location provider; 27 | 28 | ## 1.6.0+2-beta 29 | * Fix crash on start; 30 | 31 | ## 1.6.0+1-beta 32 | * Use new flutter engine; 33 | * Fix start stop bug which prevents correct state in plugin; 34 | 35 | ## 1.5.0+1 36 | * Add null safety support; 37 | 38 | ## 1.4.0+1 39 | * Set default value for autoStop; 40 | * Fix register and unregister futures never complete on Android; 41 | * Fix Doze problem for Android >= 10; 42 | 43 | ## 1.3.2+1 44 | * Fix compile error on sdk 30; 45 | * Fix app stop locating on android sdk 30 in background; 46 | 47 | ## 1.3.0+1 48 | * Add google location client as option; 49 | * Several bug fixes; 50 | 51 | ## 1.2.2+1 52 | * Add platform specific settings; 53 | * Add ability to update android notification; 54 | * Ability to showsBackgroundLocationIndicator on iOS; 55 | 56 | ## 1.1.13+1 57 | * add isServiceRunning method; 58 | 59 | ## 1.1.12+1 60 | * Added support for big text in Android notification; 61 | 62 | ## 1.1.11+1 63 | * Fix getCallbackHandle bug which caused some callbacks not getting executed; 64 | 65 | ## 1.1.10+1 66 | * Add region monitoring for iOS to get location info while app is terminated; 67 | * Minor iOS bug fix; 68 | * Add a way to use 3rd party plugins while app is terminated in iOS; 69 | 70 | ## 1.1.7+1 71 | * Add notification icon color; 72 | * Add isMocked property on location model; 73 | * Add channel name property on location dto; 74 | 75 | ## 1.1.5+1 76 | * Fix crash in onStartCommand caused by null intent on Android; 77 | * Fix getting several unwanted position on iOS; 78 | 79 | ## 1.1.3+1 80 | * Add possibility to restart locator service after reboot; 81 | * Fix triggering android notification callback with wrong notification; 82 | 83 | ## 1.1.2+2 84 | * Fix optional android notification callback. 85 | 86 | ## 1.1.2+1 87 | * Fix accessing other plugins when app is terminated. 88 | 89 | ## 1.1.1+1 90 | * ‌Fix Callback is not triggered in iOS. 91 | 92 | ## 1.1.0+1 93 | * Add callback for android notification. 94 | 95 | ## 1.0.1+2 96 | * Fix crash on detach. 97 | 98 | ## 1.0.1+1 99 | * Add isRegistered method. 100 | * Bug fixes. 101 | 102 | ## 1.0.0+1 103 | * Add auto stop feature. 104 | * Update flutter plugin library to version 2. 105 | 106 | ## 0.0.4-beta 107 | * Add parameter to setting to change android wakelock time. 108 | * Prevent service from registering twice. 109 | 110 | ## 0.0.3-beta 111 | Change where location access requested. 112 | 113 | ## 0.0.2-beta 114 | 115 | * Improvements. 116 | 117 | ## 0.0.1-beta 118 | 119 | * initial release. 120 | -------------------------------------------------------------------------------- /lib/keys.dart: -------------------------------------------------------------------------------- 1 | class Keys { 2 | static const String CHANNEL_ID = 'app.rekab/locator_plugin'; 3 | static const String BACKGROUND_CHANNEL_ID = 4 | 'app.rekab/locator_plugin_background'; 5 | 6 | static const String METHOD_SERVICE_INITIALIZED = 'LocatorService.initialized'; 7 | static const String METHOD_PLUGIN_INITIALIZE_SERVICE = 8 | 'LocatorPlugin.initializeService'; 9 | static const String METHOD_PLUGIN_REGISTER_LOCATION_UPDATE = 10 | 'LocatorPlugin.registerLocationUpdate'; 11 | static const String METHOD_PLUGIN_UN_REGISTER_LOCATION_UPDATE = 12 | 'LocatorPlugin.unRegisterLocationUpdate'; 13 | static const String METHOD_PLUGIN_IS_REGISTER_LOCATION_UPDATE = 14 | 'LocatorPlugin.isRegisterLocationUpdate'; 15 | static const String METHOD_PLUGIN_IS_SERVICE_RUNNING = 16 | 'LocatorPlugin.isServiceRunning'; 17 | static const String METHOD_PLUGIN_UPDATE_NOTIFICATION = 18 | 'LocatorPlugin.updateNotification'; 19 | 20 | static const String ARG_IS_MOCKED = 'is_mocked'; 21 | static const String ARG_LATITUDE = 'latitude'; 22 | static const String ARG_LONGITUDE = 'longitude'; 23 | static const String ARG_ALTITUDE = 'altitude'; 24 | static const String ARG_ACCURACY = 'accuracy'; 25 | static const String ARG_SPEED = 'speed'; 26 | static const String ARG_SPEED_ACCURACY = 'speed_accuracy'; 27 | static const String ARG_HEADING = 'heading'; 28 | static const String ARG_TIME = 'time'; 29 | static const String ARG_PROVIDER = 'provider'; 30 | static const String ARG_CALLBACK = 'callback'; 31 | static const String ARG_NOTIFICATION_CALLBACK = 'notificationCallback'; 32 | static const String ARG_INIT_CALLBACK = 'initCallback'; 33 | static const String ARG_INIT_DATA_CALLBACK = 'initDataCallback'; 34 | static const String ARG_DISPOSE_CALLBACK = 'disposeCallback'; 35 | static const String ARG_LOCATION = 'location'; 36 | static const String ARG_SETTINGS = 'settings'; 37 | static const String ARG_CALLBACK_DISPATCHER = 'callbackDispatcher'; 38 | 39 | static const String SETTINGS_ACCURACY = 'settings_accuracy'; 40 | static const String SETTINGS_INTERVAL = 'settings_interval'; 41 | static const String SETTINGS_DISTANCE_FILTER = 'settings_distanceFilter'; 42 | static const String SETTINGS_AUTO_STOP = 'settings_autoStop'; 43 | static const String SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME = 44 | 'settings_android_notificationChannelName'; 45 | static const String SETTINGS_ANDROID_NOTIFICATION_TITLE = 46 | 'settings_android_notificationTitle'; 47 | static const String SETTINGS_ANDROID_NOTIFICATION_MSG = 48 | 'settings_android_notificationMsg'; 49 | static const String SETTINGS_ANDROID_NOTIFICATION_BIG_MSG = 50 | 'settings_android_notificationBigMsg'; 51 | static const String SETTINGS_ANDROID_NOTIFICATION_ICON = 52 | 'settings_android_notificationIcon'; 53 | static const String SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR = 54 | 'settings_android_notificationIconColor'; 55 | static const String SETTINGS_ANDROID_WAKE_LOCK_TIME = 56 | 'settings_android_wakeLockTime'; 57 | static const String SETTINGS_ANDROID_LOCATION_CLIENT = 58 | "settings_android_location_client"; 59 | 60 | static const String SETTINGS_IOS_SHOWS_BACKGROUND_LOCATION_INDICATOR = 61 | 'settings_ios_showsBackgroundLocationIndicator'; 62 | 63 | static const String BCM_SEND_LOCATION = 'BCM_SEND_LOCATION'; 64 | static const String BCM_NOTIFICATION_CLICK = 'BCM_NOTIFICATION_CLICK'; 65 | static const String BCM_INIT = 'BCM_INIT'; 66 | static const String BCM_DISPOSE = 'BCM_DISPOSE'; 67 | } 68 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/provider/AndroidLocationProviderClient.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator.provider 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.location.Location 6 | import android.location.LocationListener 7 | import android.location.LocationManager 8 | import androidx.core.content.ContextCompat 9 | import android.os.Bundle 10 | 11 | class AndroidLocationProviderClient(context: Context, override var listener: LocationUpdateListener?) : BLLocationProvider, LocationListener { 12 | private val client: LocationManager? = 13 | ContextCompat.getSystemService(context, LocationManager::class.java) 14 | 15 | private var overrideLocation: Boolean = false 16 | private var timeOfLastLocation: Long = 0L 17 | private var timeBetweenLocation: Long = 0L 18 | 19 | @SuppressLint("MissingPermission") 20 | override fun removeLocationUpdates() { 21 | client?.removeUpdates(this) 22 | } 23 | 24 | @SuppressLint("MissingPermission") 25 | override fun requestLocationUpdates(request: LocationRequestOptions) { 26 | var gpsLocation: Location? = null 27 | var networkLocation: Location? = null 28 | timeBetweenLocation = request.interval 29 | if (client?.isProviderEnabled(LocationManager.GPS_PROVIDER) == true) { 30 | client.requestLocationUpdates(LocationManager.GPS_PROVIDER, 31 | request.interval, 32 | request.distanceFilter, 33 | this) 34 | } 35 | if (client?.isProviderEnabled(LocationManager.NETWORK_PROVIDER) == true) { 36 | client.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 37 | request.interval, 38 | request.distanceFilter, 39 | this) 40 | } 41 | gpsLocation = client?.getLastKnownLocation(LocationManager.GPS_PROVIDER) 42 | networkLocation = client?.getLastKnownLocation(LocationManager.NETWORK_PROVIDER) 43 | // return the android device last Location after start request location 44 | if (gpsLocation != null && networkLocation != null) { 45 | if (gpsLocation.time < networkLocation.time) { 46 | onLocationChanged(networkLocation) 47 | } else { 48 | onLocationChanged(gpsLocation) 49 | } 50 | } else if (gpsLocation != null) { 51 | onLocationChanged(gpsLocation) 52 | } else if (networkLocation != null) { 53 | onLocationChanged(networkLocation) 54 | } 55 | } 56 | 57 | override fun onLocationChanged(location: Location) { 58 | overrideLocation = false 59 | //whenever the expected time period is reached invalidate the last known accuracy 60 | // so that we don't just receive better and better accuracy and eventually risk receiving 61 | // only minimal locations 62 | if (location.hasAccuracy()) { 63 | if (!location.accuracy.isNaN() && 64 | location.accuracy != 0.0f && 65 | !location.accuracy.isFinite() && 66 | !location.accuracy.isInfinite()) { 67 | overrideLocation = true 68 | } 69 | } 70 | //ensure that we don't get a lot of events 71 | // or if enabled, only get more accurate events within mTimeBetweenLocationEvents 72 | if (location.time - timeOfLastLocation >= timeBetweenLocation || overrideLocation) { 73 | //be sure to store the time of receiving this event ! 74 | timeOfLastLocation = location.time 75 | //send message to parent containing the location object 76 | listener?.onLocationUpdated(LocationParserUtil.getLocationMapFromLocation(location)) 77 | } 78 | } 79 | 80 | override fun onProviderDisabled(provider: String) { 81 | // nop 82 | } 83 | 84 | override fun onProviderEnabled(provider: String) { 85 | // nop 86 | } 87 | override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {} 88 | 89 | } -------------------------------------------------------------------------------- /lib/settings/android_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:background_locator/keys.dart'; 2 | import 'package:background_locator/settings/locator_settings.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | enum LocationClient { google, android } 6 | 7 | class AndroidNotificationSettings { 8 | final String notificationChannelName; 9 | final String notificationTitle; 10 | final String notificationMsg; 11 | final String notificationBigMsg; 12 | final String notificationIcon; 13 | final Color notificationIconColor; 14 | final VoidCallback? notificationTapCallback; 15 | 16 | /// [notificationTitle] Title of the notification. Only applies for android. Default is 'Start Location Tracking'. 17 | /// 18 | /// [notificationMsg] Message of notification. Only applies for android. Default is 'Track location in background'. 19 | /// 20 | /// [notificationBigMsg] Message to be displayed in the expanded content area of the notification. Only applies for android. Default is 'Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running.'. 21 | /// 22 | /// [notificationIcon] Icon name for notification. Only applies for android. The icon should be in 'mipmap' Directory. 23 | /// Default is app icon. Icon must comply to android rules to be displayed (transparent background and black/white shape) 24 | /// 25 | /// [notificationIconColor] Icon color for notification from notification drawer. Only applies for android. Default color is grey. 26 | /// 27 | /// [notificationTapCallback] callback for notification tap 28 | /// 29 | const AndroidNotificationSettings( 30 | {this.notificationChannelName = 'Location tracking', 31 | this.notificationTitle = 'Start Location Tracking', 32 | this.notificationMsg = 'Track location in background', 33 | this.notificationBigMsg = 34 | 'Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running.', 35 | this.notificationIcon = '', 36 | this.notificationIconColor = Colors.grey, 37 | this.notificationTapCallback}); 38 | } 39 | 40 | class AndroidSettings extends LocatorSettings { 41 | final AndroidNotificationSettings androidNotificationSettings; 42 | final int wakeLockTime; 43 | final int interval; 44 | final LocationClient client; 45 | 46 | /// [accuracy] The accuracy of location, Default is max accuracy NAVIGATION. 47 | /// 48 | /// [interval] Interval of retrieving location update in second. Only applies for android. Default is 5 second. 49 | /// 50 | /// [distanceFilter] distance in meter to trigger location update, Default is 0 meter. 51 | /// 52 | /// [androidNotificationSettings] Specific setting for android notification. 53 | /// 54 | /// [wakeLockTime] Time for living service in background in minutes. Only applies in android. Default is 60 minute. 55 | const AndroidSettings( 56 | {LocationAccuracy accuracy = LocationAccuracy.NAVIGATION, 57 | this.interval = 5, 58 | double distanceFilter = 0, 59 | this.androidNotificationSettings = const AndroidNotificationSettings(), 60 | this.wakeLockTime = 60, 61 | this.client = LocationClient.google}) 62 | : super(accuracy: accuracy, distanceFilter: distanceFilter); 63 | 64 | Map toMap() { 65 | return { 66 | Keys.SETTINGS_ACCURACY: accuracy.value, 67 | Keys.SETTINGS_INTERVAL: interval, 68 | Keys.SETTINGS_DISTANCE_FILTER: distanceFilter, 69 | Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME: wakeLockTime, 70 | Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME: 71 | androidNotificationSettings.notificationChannelName, 72 | Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE: 73 | androidNotificationSettings.notificationTitle, 74 | Keys.SETTINGS_ANDROID_NOTIFICATION_MSG: 75 | androidNotificationSettings.notificationMsg, 76 | Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG: 77 | androidNotificationSettings.notificationBigMsg, 78 | Keys.SETTINGS_ANDROID_NOTIFICATION_ICON: 79 | androidNotificationSettings.notificationIcon, 80 | Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR: 81 | androidNotificationSettings.notificationIconColor.value, 82 | Keys.SETTINGS_ANDROID_LOCATION_CLIENT: client.index 83 | }; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/Keys.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator 2 | 3 | class Keys { 4 | companion object { 5 | @JvmStatic 6 | val SHARED_PREFERENCES_KEY = "SHARED_PREFERENCES_KEY" 7 | 8 | @JvmStatic 9 | val CALLBACK_DISPATCHER_HANDLE_KEY = "CALLBACK_DISPATCHER_HANDLE_KEY" 10 | 11 | @JvmStatic 12 | val CALLBACK_HANDLE_KEY = "CALLBACK_HANDLE_KEY" 13 | 14 | @JvmStatic 15 | val NOTIFICATION_CALLBACK_HANDLE_KEY = "NOTIFICATION_CALLBACK_HANDLE_KEY" 16 | 17 | @JvmStatic 18 | val INIT_CALLBACK_HANDLE_KEY = "INIT_CALLBACK_HANDLE_KEY" 19 | 20 | @JvmStatic 21 | val INIT_DATA_CALLBACK_KEY = "INIT_DATA_CALLBACK_KEY" 22 | 23 | @JvmStatic 24 | val DISPOSE_CALLBACK_HANDLE_KEY = "DISPOSE_CALLBACK_HANDLE_KEY" 25 | 26 | @JvmStatic 27 | val CHANNEL_ID = "app.rekab/locator_plugin" 28 | 29 | @JvmStatic 30 | val BACKGROUND_CHANNEL_ID = "app.rekab/locator_plugin_background" 31 | 32 | @JvmStatic 33 | val METHOD_SERVICE_INITIALIZED = "LocatorService.initialized" 34 | 35 | @JvmStatic 36 | val METHOD_PLUGIN_INITIALIZE_SERVICE = "LocatorPlugin.initializeService" 37 | 38 | @JvmStatic 39 | val METHOD_PLUGIN_REGISTER_LOCATION_UPDATE = "LocatorPlugin.registerLocationUpdate" 40 | 41 | @JvmStatic 42 | val METHOD_PLUGIN_UN_REGISTER_LOCATION_UPDATE = "LocatorPlugin.unRegisterLocationUpdate" 43 | 44 | @JvmStatic 45 | val METHOD_PLUGIN_IS_REGISTER_LOCATION_UPDATE = "LocatorPlugin.isRegisterLocationUpdate" 46 | 47 | @JvmStatic 48 | val METHOD_PLUGIN_IS_SERVICE_RUNNING = "LocatorPlugin.isServiceRunning" 49 | 50 | @JvmStatic 51 | val METHOD_PLUGIN_UPDATE_NOTIFICATION = "LocatorPlugin.updateNotification" 52 | 53 | @JvmStatic 54 | val ARG_INIT_CALLBACK = "initCallback" 55 | 56 | @JvmStatic 57 | val ARG_INIT_DATA_CALLBACK = "initDataCallback" 58 | 59 | @JvmStatic 60 | val ARG_DISPOSE_CALLBACK = "disposeCallback" 61 | 62 | @JvmStatic 63 | val ARG_IS_MOCKED = "is_mocked" 64 | 65 | @JvmStatic 66 | val ARG_LATITUDE = "latitude" 67 | 68 | @JvmStatic 69 | val ARG_LONGITUDE = "longitude" 70 | 71 | @JvmStatic 72 | val ARG_ACCURACY = "accuracy" 73 | 74 | @JvmStatic 75 | val ARG_ALTITUDE = "altitude" 76 | 77 | @JvmStatic 78 | val ARG_SPEED = "speed" 79 | 80 | @JvmStatic 81 | val ARG_SPEED_ACCURACY = "speed_accuracy" 82 | 83 | @JvmStatic 84 | val ARG_HEADING = "heading" 85 | 86 | @JvmStatic 87 | val ARG_TIME = "time" 88 | 89 | @JvmStatic 90 | val ARG_PROVIDER = "provider" 91 | 92 | @JvmStatic 93 | val ARG_CALLBACK = "callback" 94 | 95 | @JvmStatic 96 | val ARG_NOTIFICATION_CALLBACK = "notificationCallback" 97 | 98 | @JvmStatic 99 | val ARG_LOCATION = "location" 100 | 101 | @JvmStatic 102 | val ARG_SETTINGS = "settings" 103 | 104 | @JvmStatic 105 | val ARG_CALLBACK_DISPATCHER = "callbackDispatcher" 106 | 107 | 108 | @JvmStatic 109 | val SETTINGS_ACCURACY = "settings_accuracy" 110 | 111 | @JvmStatic 112 | val SETTINGS_INTERVAL = "settings_interval" 113 | 114 | @JvmStatic 115 | val SETTINGS_DISTANCE_FILTER = "settings_distanceFilter" 116 | 117 | @JvmStatic 118 | val SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME = "settings_android_notificationChannelName" 119 | 120 | @JvmStatic 121 | val SETTINGS_ANDROID_NOTIFICATION_TITLE = "settings_android_notificationTitle" 122 | 123 | @JvmStatic 124 | val SETTINGS_ANDROID_NOTIFICATION_MSG = "settings_android_notificationMsg" 125 | 126 | @JvmStatic 127 | val SETTINGS_ANDROID_NOTIFICATION_BIG_MSG = "settings_android_notificationBigMsg" 128 | 129 | @JvmStatic 130 | val SETTINGS_ANDROID_NOTIFICATION_ICON = "settings_android_notificationIcon" 131 | 132 | @JvmStatic 133 | val SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR = "settings_android_notificationIconColor" 134 | 135 | @JvmStatic 136 | val SETTINGS_ANDROID_WAKE_LOCK_TIME = "settings_android_wakeLockTime" 137 | 138 | @JvmStatic 139 | val SETTINGS_ANDROID_LOCATION_CLIENT = "settings_android_location_client" 140 | 141 | @JvmStatic 142 | val SETTINGS_INIT_PLUGGABLE = "settings_init_pluggable" 143 | 144 | @JvmStatic 145 | val SETTINGS_DISPOSABLE_PLUGGABLE = "settings_disposable_pluggable" 146 | 147 | @JvmStatic 148 | val BCM_SEND_LOCATION = "BCM_SEND_LOCATION" 149 | 150 | @JvmStatic 151 | val BCM_NOTIFICATION_CLICK = "BCM_NOTIFICATION_CLICK" 152 | 153 | @JvmStatic 154 | val BCM_INIT = "BCM_INIT" 155 | 156 | @JvmStatic 157 | val BCM_DISPOSE = "BCM_DISPOSE" 158 | 159 | @JvmStatic 160 | val NOTIFICATION_ACTION = "com.rekab.background_locator.notification" 161 | } 162 | } -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:ffi'; 3 | import 'dart:isolate'; 4 | import 'dart:ui'; 5 | 6 | import 'package:background_locator/background_locator.dart'; 7 | import 'package:background_locator/location_dto.dart'; 8 | import 'package:background_locator/settings/android_settings.dart'; 9 | import 'package:background_locator/settings/ios_settings.dart'; 10 | import 'package:background_locator/settings/locator_settings.dart'; 11 | import 'package:flutter/material.dart'; 12 | import 'package:location_permissions/location_permissions.dart'; 13 | 14 | import 'file_manager.dart'; 15 | import 'location_callback_handler.dart'; 16 | import 'location_service_repository.dart'; 17 | 18 | void main() => runApp(MyApp()); 19 | 20 | class MyApp extends StatefulWidget { 21 | @override 22 | _MyAppState createState() => _MyAppState(); 23 | } 24 | 25 | class _MyAppState extends State { 26 | ReceivePort port = ReceivePort(); 27 | 28 | String logStr = ''; 29 | bool isRunning; 30 | LocationDto lastLocation; 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | 36 | if (IsolateNameServer.lookupPortByName( 37 | LocationServiceRepository.isolateName) != 38 | null) { 39 | IsolateNameServer.removePortNameMapping( 40 | LocationServiceRepository.isolateName); 41 | } 42 | 43 | IsolateNameServer.registerPortWithName( 44 | port.sendPort, LocationServiceRepository.isolateName); 45 | 46 | port.listen( 47 | (dynamic data) async { 48 | await updateUI(data); 49 | }, 50 | ); 51 | initPlatformState(); 52 | } 53 | 54 | @override 55 | void dispose() { 56 | super.dispose(); 57 | } 58 | 59 | Future updateUI(LocationDto data) async { 60 | final log = await FileManager.readLogFile(); 61 | 62 | await _updateNotificationText(data); 63 | 64 | setState(() { 65 | if (data != null) { 66 | lastLocation = data; 67 | } 68 | logStr = log; 69 | }); 70 | } 71 | 72 | Future _updateNotificationText(LocationDto data) async { 73 | if (data == null) { 74 | return; 75 | } 76 | 77 | await BackgroundLocator.updateNotificationText( 78 | title: "new location received", 79 | msg: "${DateTime.now()}", 80 | bigMsg: "${data.latitude}, ${data.longitude}"); 81 | } 82 | 83 | Future initPlatformState() async { 84 | print('Initializing...'); 85 | await BackgroundLocator.initialize(); 86 | logStr = await FileManager.readLogFile(); 87 | print('Initialization done'); 88 | final _isRunning = await BackgroundLocator.isServiceRunning(); 89 | setState(() { 90 | isRunning = _isRunning; 91 | }); 92 | print('Running ${isRunning.toString()}'); 93 | } 94 | 95 | @override 96 | Widget build(BuildContext context) { 97 | final start = SizedBox( 98 | width: double.maxFinite, 99 | child: ElevatedButton( 100 | child: Text('Start'), 101 | onPressed: () { 102 | _onStart(); 103 | }, 104 | ), 105 | ); 106 | final stop = SizedBox( 107 | width: double.maxFinite, 108 | child: ElevatedButton( 109 | child: Text('Stop'), 110 | onPressed: () { 111 | onStop(); 112 | }, 113 | ), 114 | ); 115 | final clear = SizedBox( 116 | width: double.maxFinite, 117 | child: ElevatedButton( 118 | child: Text('Clear Log'), 119 | onPressed: () { 120 | FileManager.clearLogFile(); 121 | setState(() { 122 | logStr = ''; 123 | }); 124 | }, 125 | ), 126 | ); 127 | String msgStatus = "-"; 128 | if (isRunning != null) { 129 | if (isRunning) { 130 | msgStatus = 'Is running'; 131 | } else { 132 | msgStatus = 'Is not running'; 133 | } 134 | } 135 | final status = Text("Status: $msgStatus"); 136 | 137 | final log = Text( 138 | logStr, 139 | ); 140 | 141 | return MaterialApp( 142 | home: Scaffold( 143 | appBar: AppBar( 144 | title: const Text('Flutter background Locator'), 145 | ), 146 | body: Container( 147 | width: double.maxFinite, 148 | padding: const EdgeInsets.all(22), 149 | child: SingleChildScrollView( 150 | child: Column( 151 | crossAxisAlignment: CrossAxisAlignment.center, 152 | children: [start, stop, clear, status, log], 153 | ), 154 | ), 155 | ), 156 | ), 157 | ); 158 | } 159 | 160 | void onStop() async { 161 | await BackgroundLocator.unRegisterLocationUpdate(); 162 | final _isRunning = await BackgroundLocator.isServiceRunning(); 163 | setState(() { 164 | isRunning = _isRunning; 165 | }); 166 | } 167 | 168 | void _onStart() async { 169 | if (await _checkLocationPermission()) { 170 | await _startLocator(); 171 | final _isRunning = await BackgroundLocator.isServiceRunning(); 172 | 173 | setState(() { 174 | isRunning = _isRunning; 175 | lastLocation = null; 176 | }); 177 | } else { 178 | // show error 179 | } 180 | } 181 | 182 | Future _checkLocationPermission() async { 183 | final access = await LocationPermissions().checkPermissionStatus(); 184 | switch (access) { 185 | case PermissionStatus.unknown: 186 | case PermissionStatus.denied: 187 | case PermissionStatus.restricted: 188 | final permission = await LocationPermissions().requestPermissions( 189 | permissionLevel: LocationPermissionLevel.locationAlways, 190 | ); 191 | if (permission == PermissionStatus.granted) { 192 | return true; 193 | } else { 194 | return false; 195 | } 196 | break; 197 | case PermissionStatus.granted: 198 | return true; 199 | break; 200 | default: 201 | return false; 202 | break; 203 | } 204 | } 205 | 206 | Future _startLocator() async{ 207 | Map data = {'countInit': 1}; 208 | return await BackgroundLocator.registerLocationUpdate(LocationCallbackHandler.callback, 209 | initCallback: LocationCallbackHandler.initCallback, 210 | initDataCallback: data, 211 | disposeCallback: LocationCallbackHandler.disposeCallback, 212 | iosSettings: IOSSettings( 213 | accuracy: LocationAccuracy.NAVIGATION, distanceFilter: 0), 214 | autoStop: false, 215 | androidSettings: AndroidSettings( 216 | accuracy: LocationAccuracy.NAVIGATION, 217 | interval: 5, 218 | distanceFilter: 0, 219 | client: LocationClient.google, 220 | androidNotificationSettings: AndroidNotificationSettings( 221 | notificationChannelName: 'Location tracking', 222 | notificationTitle: 'Start Location Tracking', 223 | notificationMsg: 'Track location in background', 224 | notificationBigMsg: 225 | 'Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running.', 226 | notificationIconColor: Colors.grey, 227 | notificationTapCallback: 228 | LocationCallbackHandler.notificationCallback))); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | ######################### 2 | # .gitignore file for Xcode4 and Xcode5 Source projects 3 | # 4 | # Apple bugs, waiting for Apple to fix/respond: 5 | # 6 | # 15564624 - what does the xccheckout file in Xcode5 do? Where's the documentation? 7 | # 8 | # Version 2.6 9 | # For latest version, see: http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects 10 | # 11 | # 2015 updates: 12 | # - Fixed typo in "xccheckout" line - thanks to @lyck for pointing it out! 13 | # - Fixed the .idea optional ignore. Thanks to @hashier for pointing this out 14 | # - Finally added "xccheckout" to the ignore. Apple still refuses to answer support requests about this, but in practice it seems you should ignore it. 15 | # - minor tweaks from Jona and Coeur (slightly more precise xc* filtering/names) 16 | # 2014 updates: 17 | # - appended non-standard items DISABLED by default (uncomment if you use those tools) 18 | # - removed the edit that an SO.com moderator made without bothering to ask me 19 | # - researched CocoaPods .lock more carefully, thanks to Gokhan Celiker 20 | # 2013 updates: 21 | # - fixed the broken "save personal Schemes" 22 | # - added line-by-line explanations for EVERYTHING (some were missing) 23 | # 24 | # NB: if you are storing "built" products, this WILL NOT WORK, 25 | # and you should use a different .gitignore (or none at all) 26 | # This file is for SOURCE projects, where there are many extra 27 | # files that we want to exclude 28 | # 29 | ######################### 30 | 31 | ##### 32 | # OS X temporary files that should never be committed 33 | # 34 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html 35 | 36 | .DS_Store 37 | 38 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html 39 | 40 | .Trashes 41 | 42 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html 43 | 44 | *.swp 45 | 46 | # 47 | # *.lock - this is used and abused by many editors for many different things. 48 | # For the main ones I use (e.g. Eclipse), it should be excluded 49 | # from source-control, but YMMV. 50 | # (lock files are usually local-only file-synchronization on the local FS that should NOT go in git) 51 | # c.f. the "OPTIONAL" section at bottom though, for tool-specific variations! 52 | # 53 | # In particular, if you're using CocoaPods, you'll want to comment-out this line: 54 | #.lock 55 | 56 | 57 | # 58 | # profile - REMOVED temporarily (on double-checking, I can't find it in OS X docs?) 59 | #profile 60 | 61 | 62 | #### 63 | # Xcode temporary files that should never be committed 64 | # 65 | # NB: NIB/XIB files still exist even on Storyboard projects, so we want this... 66 | 67 | #*~.nib 68 | 69 | 70 | #### 71 | # Xcode build files - 72 | # 73 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "DerivedData" 74 | 75 | DerivedData/ 76 | 77 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "build" 78 | 79 | build/ 80 | Build/ 81 | Index/ 82 | Pods/ 83 | 84 | 85 | ##### 86 | # Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups) 87 | # 88 | # This is complicated: 89 | # 90 | # SOMETIMES you need to put this file in version control. 91 | # Apple designed it poorly - if you use "custom executables", they are 92 | # saved in this file. 93 | # 99% of projects do NOT use those, so they do NOT want to version control this file. 94 | # ..but if you're in the 1%, comment out the line "*.pbxuser" 95 | 96 | # .pbxuser: http://lists.apple.com/archives/xcode-users/2004/Jan/msg00193.html 97 | 98 | *.pbxuser 99 | 100 | # .mode1v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html 101 | 102 | *.mode1v3 103 | 104 | # .mode2v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html 105 | 106 | *.mode2v3 107 | 108 | # .perspectivev3: http://stackoverflow.com/questions/5223297/xcode-projects-what-is-a-perspectivev3-file 109 | 110 | *.perspectivev3 111 | 112 | # NB: also, whitelist the default ones, some projects need to use these 113 | !default.pbxuser 114 | !default.mode1v3 115 | !default.mode2v3 116 | !default.perspectivev3 117 | 118 | 119 | #### 120 | # Xcode 4 - semi-personal settings 121 | # 122 | # Apple Shared data that Apple put in the wrong folder 123 | # c.f. http://stackoverflow.com/a/19260712/153422 124 | # FROM ANSWER: Apple says "don't ignore it" 125 | # FROM COMMENTS: Apple is wrong; Apple code is too buggy to trust; there are no known negative side-effects to ignoring Apple's unofficial advice and instead doing the thing that actively fixes bugs in Xcode 126 | # Up to you, but ... current advice: ignore it. 127 | *.xccheckout 128 | 129 | # 130 | # 131 | # OPTION 1: --------------------------------- 132 | # throw away ALL personal settings (including custom schemes! 133 | # - unless they are "shared") 134 | # As per build/ and DerivedData/, this ought to have a trailing slash 135 | # 136 | # NB: this is exclusive with OPTION 2 below 137 | xcuserdata/ 138 | 139 | # OPTION 2: --------------------------------- 140 | # get rid of ALL personal settings, but KEEP SOME OF THEM 141 | # - NB: you must manually uncomment the bits you want to keep 142 | # 143 | # NB: this *requires* git v1.8.2 or above; you may need to upgrade to latest OS X, 144 | # or manually install git over the top of the OS X version 145 | # NB: this is exclusive with OPTION 1 above 146 | # 147 | #xcuserdata/**/* 148 | 149 | # (requires option 2 above): Personal Schemes 150 | # 151 | #!xcuserdata/**/xcschemes/* 152 | 153 | #### 154 | # XCode 4 workspaces - more detailed 155 | # 156 | # Workspaces are important! They are a core feature of Xcode - don't exclude them :) 157 | # 158 | # Workspace layout is quite spammy. For reference: 159 | # 160 | # /(root)/ 161 | # /(project-name).xcodeproj/ 162 | # project.pbxproj 163 | # /project.xcworkspace/ 164 | # contents.xcworkspacedata 165 | # /xcuserdata/ 166 | # /(your name)/xcuserdatad/ 167 | # UserInterfaceState.xcuserstate 168 | # /xcshareddata/ 169 | # /xcschemes/ 170 | # (shared scheme name).xcscheme 171 | # /xcuserdata/ 172 | # /(your name)/xcuserdatad/ 173 | # (private scheme).xcscheme 174 | # xcschememanagement.plist 175 | # 176 | # 177 | 178 | #### 179 | # Xcode 4 - Deprecated classes 180 | # 181 | # Allegedly, if you manually "deprecate" your classes, they get moved here. 182 | # 183 | # We're using source-control, so this is a "feature" that we do not want! 184 | 185 | *.moved-aside 186 | 187 | #### 188 | # OPTIONAL: Some well-known tools that people use side-by-side with Xcode / iOS development 189 | # 190 | # NB: I'd rather not include these here, but gitignore's design is weak and doesn't allow 191 | # modular gitignore: you have to put EVERYTHING in one file. 192 | # 193 | # COCOAPODS: 194 | # 195 | # c.f. http://guides.cocoapods.org/using/using-cocoapods.html#what-is-a-podfilelock 196 | # c.f. http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 197 | # 198 | #!Podfile.lock 199 | # 200 | # RUBY: 201 | # 202 | # c.f. http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/ 203 | # 204 | #!Gemfile.lock 205 | # 206 | # IDEA: 207 | # 208 | # c.f. https://www.jetbrains.com/objc/help/managing-projects-under-version-control.html?search=workspace.xml 209 | # 210 | #.idea/workspace.xml 211 | # 212 | # TEXTMATE: 213 | # 214 | # -- UNVERIFIED: c.f. http://stackoverflow.com/a/50283/153422 215 | # 216 | #tm_build_errors 217 | 218 | #### 219 | # UNKNOWN: recommended by others, but I can't discover what these files are 220 | # 221 | RekabNetwork 222 | -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/PreferencesManager.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator 2 | 3 | import android.content.Context 4 | import com.google.gson.Gson 5 | import com.google.gson.reflect.TypeToken 6 | import rekab.app.background_locator.provider.LocationClient 7 | 8 | class PreferencesManager { 9 | companion object { 10 | private const val PREF_NAME = "background_locator" 11 | 12 | @JvmStatic 13 | fun saveCallbackDispatcher(context: Context, map: Map) { 14 | val sharedPreferences = 15 | context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) 16 | 17 | sharedPreferences.edit() 18 | .putLong(Keys.ARG_CALLBACK_DISPATCHER, 19 | map[Keys.ARG_CALLBACK_DISPATCHER] as Long) 20 | .apply() 21 | } 22 | 23 | @JvmStatic 24 | fun saveSettings(context: Context, map: Map) { 25 | val sharedPreferences = 26 | context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) 27 | 28 | sharedPreferences.edit() 29 | .putLong(Keys.ARG_CALLBACK, 30 | map[Keys.ARG_CALLBACK] as Long) 31 | .apply() 32 | 33 | if (map[Keys.ARG_NOTIFICATION_CALLBACK] as? Long != null) { 34 | sharedPreferences.edit() 35 | .putLong(Keys.ARG_NOTIFICATION_CALLBACK, 36 | map[Keys.ARG_NOTIFICATION_CALLBACK] as Long) 37 | .apply() 38 | } 39 | 40 | val settings = map[Keys.ARG_SETTINGS] as Map<*, *> 41 | 42 | sharedPreferences.edit() 43 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME, 44 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME] as String) 45 | .apply() 46 | 47 | sharedPreferences.edit() 48 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE, 49 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE] as String) 50 | .apply() 51 | 52 | sharedPreferences.edit() 53 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG, 54 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_MSG] as String) 55 | .apply() 56 | 57 | sharedPreferences.edit() 58 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG, 59 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG] as String) 60 | .apply() 61 | 62 | sharedPreferences.edit() 63 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON, 64 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON] as String) 65 | .apply() 66 | 67 | sharedPreferences.edit() 68 | .putLong(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR, 69 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR] as Long) 70 | .apply() 71 | 72 | sharedPreferences.edit() 73 | .putInt(Keys.SETTINGS_INTERVAL, 74 | settings[Keys.SETTINGS_INTERVAL] as Int) 75 | .apply() 76 | 77 | sharedPreferences.edit() 78 | .putInt(Keys.SETTINGS_ACCURACY, 79 | settings[Keys.SETTINGS_ACCURACY] as Int) 80 | .apply() 81 | 82 | sharedPreferences.edit() 83 | .putFloat(Keys.SETTINGS_DISTANCE_FILTER, 84 | (settings[Keys.SETTINGS_DISTANCE_FILTER] as Double).toFloat()) 85 | .apply() 86 | 87 | if (settings.containsKey(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME)) { 88 | sharedPreferences.edit() 89 | .putInt(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME, 90 | settings[Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME] as Int) 91 | .apply() 92 | } 93 | 94 | sharedPreferences.edit() 95 | .putInt(Keys.SETTINGS_ANDROID_LOCATION_CLIENT, 96 | settings[Keys.SETTINGS_ANDROID_LOCATION_CLIENT] as Int) 97 | .apply() 98 | } 99 | 100 | @JvmStatic 101 | fun getSettings(context: Context): Map { 102 | val sharedPreferences = 103 | context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) 104 | 105 | val result = HashMap() 106 | 107 | result[Keys.ARG_CALLBACK_DISPATCHER] = sharedPreferences.getLong(Keys.ARG_CALLBACK_DISPATCHER, 0) 108 | result[Keys.ARG_CALLBACK] = sharedPreferences.getLong(Keys.ARG_CALLBACK, 0) 109 | 110 | if (sharedPreferences.contains(Keys.ARG_NOTIFICATION_CALLBACK)) { 111 | result[Keys.ARG_NOTIFICATION_CALLBACK] = 112 | sharedPreferences.getLong(Keys.ARG_NOTIFICATION_CALLBACK, 0) 113 | } 114 | 115 | val settings = HashMap() 116 | 117 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME] = 118 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME, "") 119 | 120 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE] = 121 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE, "") 122 | 123 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_MSG] = 124 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG, "") 125 | 126 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG] = 127 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG, "") 128 | 129 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON] = 130 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON, "") 131 | 132 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR] = 133 | sharedPreferences.getLong(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR, 0) 134 | 135 | settings[Keys.SETTINGS_INTERVAL] = 136 | sharedPreferences.getInt(Keys.SETTINGS_INTERVAL, 0) 137 | 138 | settings[Keys.SETTINGS_ACCURACY] = 139 | sharedPreferences.getInt(Keys.SETTINGS_ACCURACY, 0) 140 | 141 | settings[Keys.SETTINGS_DISTANCE_FILTER] = 142 | sharedPreferences.getFloat(Keys.SETTINGS_DISTANCE_FILTER, 0f).toDouble() 143 | 144 | if (sharedPreferences.contains(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME)) { 145 | settings[Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME] = sharedPreferences.getInt(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME, 0) 146 | } 147 | 148 | settings[Keys.SETTINGS_ANDROID_LOCATION_CLIENT] = 149 | sharedPreferences.getInt(Keys.SETTINGS_ANDROID_LOCATION_CLIENT, 0) 150 | 151 | result[Keys.ARG_SETTINGS] = settings 152 | return result 153 | } 154 | 155 | @JvmStatic 156 | fun getLocationClient(context: Context): LocationClient { 157 | val sharedPreferences = 158 | context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) 159 | val client = sharedPreferences.getInt(Keys.SETTINGS_ANDROID_LOCATION_CLIENT, 0) 160 | return LocationClient.fromInt(client) ?: LocationClient.Google 161 | } 162 | 163 | @JvmStatic 164 | fun setCallbackHandle(context: Context, key: String, handle: Long?) { 165 | if (handle == null) { 166 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 167 | .edit() 168 | .remove(key) 169 | .apply() 170 | return 171 | } 172 | 173 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 174 | .edit() 175 | .putLong(key, handle) 176 | .apply() 177 | } 178 | 179 | @JvmStatic 180 | fun setDataCallback(context: Context, key: String, data: Map<*, *>?) { 181 | if (data == null) { 182 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 183 | .edit() 184 | .remove(key) 185 | .apply() 186 | return 187 | } 188 | val dataStr = Gson().toJson(data) 189 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 190 | .edit() 191 | .putString(key, dataStr) 192 | .apply() 193 | } 194 | 195 | @JvmStatic 196 | fun getCallbackHandle(context: Context, key: String): Long? { 197 | val sharedPreferences = context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 198 | if (sharedPreferences.contains(key)) return sharedPreferences.getLong(key, 0L) 199 | return null 200 | } 201 | 202 | @JvmStatic 203 | fun getDataCallback(context: Context, key: String): Map<*, *> { 204 | val initialDataStr = context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 205 | .getString(key, null) 206 | val type = object : TypeToken>() {}.type 207 | return Gson().fromJson(initialDataStr, type) 208 | } 209 | } 210 | } -------------------------------------------------------------------------------- /ios/Classes/BackgroundLocatorPlugin.m: -------------------------------------------------------------------------------- 1 | #import "BackgroundLocatorPlugin.h" 2 | #import "Globals.h" 3 | #import "Utils/Util.h" 4 | #import "Preferences/PreferencesManager.h" 5 | #import "InitPluggable.h" 6 | #import "DisposePluggable.h" 7 | 8 | @implementation BackgroundLocatorPlugin { 9 | FlutterEngine *_headlessRunner; 10 | FlutterMethodChannel *_callbackChannel; 11 | FlutterMethodChannel *_mainChannel; 12 | NSObject *_registrar; 13 | CLLocationManager *_locationManager; 14 | CLLocation* _lastLocation; 15 | } 16 | 17 | static FlutterPluginRegistrantCallback registerPlugins = nil; 18 | static BackgroundLocatorPlugin *instance = nil; 19 | 20 | #pragma mark FlutterPlugin Methods 21 | 22 | + (void)registerWithRegistrar:(nonnull NSObject *)registrar { 23 | @synchronized(self) { 24 | if (instance == nil) { 25 | instance = [[BackgroundLocatorPlugin alloc] init:registrar]; 26 | [registrar addApplicationDelegate:instance]; 27 | } 28 | } 29 | } 30 | 31 | + (void)setPluginRegistrantCallback:(FlutterPluginRegistrantCallback)callback { 32 | registerPlugins = callback; 33 | } 34 | 35 | + (BackgroundLocatorPlugin *) getInstance { 36 | return instance; 37 | } 38 | 39 | - (void)invokeMethod:(NSString*_Nonnull)method arguments:(id _Nullable)arguments { 40 | // Return if flutter engine is not ready 41 | NSString *isolateId = [_headlessRunner isolateId]; 42 | if (_callbackChannel == nil || isolateId == nil) { 43 | return; 44 | } 45 | 46 | [_callbackChannel invokeMethod:method arguments:arguments]; 47 | } 48 | 49 | - (void)handleMethodCall:(FlutterMethodCall *)call 50 | result:(FlutterResult)result { 51 | MethodCallHelper *callHelper = [[MethodCallHelper alloc] init]; 52 | [callHelper handleMethodCall:call result:result delegate:self]; 53 | } 54 | 55 | //https://medium.com/@calvinlin_96474/ios-11-continuous-background-location-update-by-swift-4-12ce3ac603e3 56 | // iOS will launch the app when new location received 57 | - (BOOL)application:(UIApplication *)application 58 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 59 | // Check to see if we're being launched due to a location event. 60 | if (launchOptions[UIApplicationLaunchOptionsLocationKey] != nil) { 61 | // Restart the headless service. 62 | [self startLocatorService:[PreferencesManager getCallbackDispatcherHandle]]; 63 | [PreferencesManager setObservingRegion:YES]; 64 | } else if([PreferencesManager isObservingRegion]) { 65 | [self prepareLocationManager]; 66 | [self removeLocator]; 67 | [PreferencesManager setObservingRegion:NO]; 68 | [_locationManager startUpdatingLocation]; 69 | } 70 | 71 | // Note: if we return NO, this vetos the launch of the application. 72 | return YES; 73 | } 74 | 75 | - (void)applicationDidEnterBackground:(UIApplication *)application { 76 | if ([PreferencesManager isServiceRunning]) { 77 | [_locationManager startMonitoringSignificantLocationChanges]; 78 | } 79 | } 80 | 81 | -(void)applicationWillTerminate:(UIApplication *)application { 82 | [self observeRegionForLocation:_lastLocation]; 83 | } 84 | 85 | - (void) observeRegionForLocation:(CLLocation *)location { 86 | double distanceFilter = [PreferencesManager getDistanceFilter]; 87 | CLRegion* region = [[CLCircularRegion alloc] initWithCenter:location.coordinate 88 | radius:distanceFilter 89 | identifier:@"region"]; 90 | region.notifyOnEntry = false; 91 | region.notifyOnExit = true; 92 | [_locationManager startMonitoringForRegion:region]; 93 | } 94 | 95 | - (void) prepareLocationMap:(CLLocation*) location { 96 | _lastLocation = location; 97 | NSDictionary* locationMap = [Util getLocationMap:location]; 98 | 99 | [self sendLocationEvent:locationMap]; 100 | } 101 | 102 | #pragma mark LocationManagerDelegate Methods 103 | - (void)locationManager:(CLLocationManager *)manager 104 | didUpdateLocations:(NSArray *)locations { 105 | if (locations.count > 0) { 106 | CLLocation* location = [locations objectAtIndex:0]; 107 | [self prepareLocationMap: location]; 108 | if([PreferencesManager isObservingRegion]) { 109 | [self observeRegionForLocation: location]; 110 | [_locationManager stopUpdatingLocation]; 111 | } 112 | } 113 | } 114 | 115 | - (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region { 116 | [_locationManager stopMonitoringForRegion:region]; 117 | [_locationManager startUpdatingLocation]; 118 | } 119 | 120 | #pragma mark LocatorPlugin Methods 121 | - (void) sendLocationEvent: (NSDictionary*)location { 122 | NSString *isolateId = [_headlessRunner isolateId]; 123 | if (_callbackChannel == nil || isolateId == nil) { 124 | return; 125 | } 126 | 127 | NSDictionary *map = @{ 128 | kArgCallback : @([PreferencesManager getCallbackHandle:kCallbackKey]), 129 | kArgLocation: location 130 | }; 131 | [_callbackChannel invokeMethod:kBCMSendLocation arguments:map]; 132 | } 133 | 134 | - (instancetype)init:(NSObject *)registrar { 135 | self = [super init]; 136 | 137 | _headlessRunner = [[FlutterEngine alloc] initWithName:@"LocatorIsolate" project:nil allowHeadlessExecution:YES]; 138 | _registrar = registrar; 139 | [self prepareLocationManager]; 140 | 141 | _mainChannel = [FlutterMethodChannel methodChannelWithName:kChannelId 142 | binaryMessenger:[registrar messenger]]; 143 | [registrar addMethodCallDelegate:self channel:_mainChannel]; 144 | 145 | _callbackChannel = 146 | [FlutterMethodChannel methodChannelWithName:kBackgroundChannelId 147 | binaryMessenger:[_headlessRunner binaryMessenger] ]; 148 | return self; 149 | } 150 | 151 | - (void) prepareLocationManager { 152 | _locationManager = [[CLLocationManager alloc] init]; 153 | [_locationManager setDelegate:self]; 154 | _locationManager.pausesLocationUpdatesAutomatically = NO; 155 | } 156 | 157 | #pragma mark MethodCallHelperDelegate 158 | 159 | - (void)startLocatorService:(int64_t)handle { 160 | [PreferencesManager setCallbackDispatcherHandle:handle]; 161 | FlutterCallbackInformation *info = [FlutterCallbackCache lookupCallbackInformation:handle]; 162 | NSAssert(info != nil, @"failed to find callback"); 163 | 164 | NSString *entrypoint = info.callbackName; 165 | NSString *uri = info.callbackLibraryPath; 166 | [_headlessRunner runWithEntrypoint:entrypoint libraryURI:uri]; 167 | NSAssert(registerPlugins != nil, @"failed to set registerPlugins"); 168 | 169 | // Once our headless runner has been started, we need to register the application's plugins 170 | // with the runner in order for them to work on the background isolate. `registerPlugins` is 171 | // a callback set from AppDelegate.m in the main application. This callback should register 172 | // all relevant plugins (excluding those which require UI). 173 | static dispatch_once_t onceToken; 174 | dispatch_once(&onceToken, ^{ 175 | registerPlugins(_headlessRunner); 176 | }); 177 | [_registrar addMethodCallDelegate:self channel:_callbackChannel]; 178 | } 179 | 180 | - (void)registerLocator:(int64_t)callback 181 | initCallback:(int64_t)initCallback 182 | initialDataDictionary:(NSDictionary*)initialDataDictionary 183 | disposeCallback:(int64_t)disposeCallback 184 | settings: (NSDictionary*)settings { 185 | [self->_locationManager requestAlwaysAuthorization]; 186 | 187 | long accuracyKey = [[settings objectForKey:kSettingsAccuracy] longValue]; 188 | CLLocationAccuracy accuracy = [Util getAccuracy:accuracyKey]; 189 | double distanceFilter= [[settings objectForKey:kSettingsDistanceFilter] doubleValue]; 190 | bool showsBackgroundLocationIndicator=[[settings objectForKey:kSettingsShowsBackgroundLocationIndicator] boolValue]; 191 | 192 | _locationManager.desiredAccuracy = accuracy; 193 | _locationManager.distanceFilter = distanceFilter; 194 | 195 | if (@available(iOS 11.0, *)) { 196 | _locationManager.showsBackgroundLocationIndicator = showsBackgroundLocationIndicator; 197 | } 198 | 199 | if (@available(iOS 9.0, *)) { 200 | _locationManager.allowsBackgroundLocationUpdates = YES; 201 | } 202 | 203 | [PreferencesManager saveDistanceFilter:distanceFilter]; 204 | 205 | [PreferencesManager setCallbackHandle:callback key:kCallbackKey]; 206 | 207 | InitPluggable *initPluggable = [[InitPluggable alloc] init]; 208 | [initPluggable setCallback:initCallback]; 209 | [initPluggable onServiceStart:initialDataDictionary]; 210 | 211 | DisposePluggable *disposePluggable = [[DisposePluggable alloc] init]; 212 | [disposePluggable setCallback:disposeCallback]; 213 | 214 | [_locationManager startUpdatingLocation]; 215 | [_locationManager startMonitoringSignificantLocationChanges]; 216 | } 217 | 218 | - (void)removeLocator { 219 | if (_locationManager == nil) { 220 | return; 221 | } 222 | 223 | @synchronized (self) { 224 | [_locationManager stopUpdatingLocation]; 225 | 226 | if (@available(iOS 9.0, *)) { 227 | _locationManager.allowsBackgroundLocationUpdates = NO; 228 | } 229 | 230 | [_locationManager stopMonitoringSignificantLocationChanges]; 231 | 232 | for (CLRegion* region in [_locationManager monitoredRegions]) { 233 | [_locationManager stopMonitoringForRegion:region]; 234 | } 235 | } 236 | 237 | DisposePluggable *disposePluggable = [[DisposePluggable alloc] init]; 238 | [disposePluggable onServiceDispose]; 239 | } 240 | 241 | - (void) setServiceRunning:(BOOL) value { 242 | @synchronized(self) { 243 | [PreferencesManager setServiceRunning:value]; 244 | } 245 | } 246 | 247 | - (BOOL)isServiceRunning{ 248 | return [PreferencesManager isServiceRunning]; 249 | } 250 | 251 | @end 252 | -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/IsolateHolderService.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator 2 | 3 | import android.app.* 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.os.Build 7 | import android.os.Handler 8 | import android.os.IBinder 9 | import android.os.PowerManager 10 | import android.util.Log 11 | import androidx.core.app.NotificationCompat 12 | import io.flutter.FlutterInjector 13 | import io.flutter.embedding.engine.FlutterEngine 14 | import io.flutter.plugin.common.MethodCall 15 | import io.flutter.plugin.common.MethodChannel 16 | import rekab.app.background_locator.pluggables.DisposePluggable 17 | import rekab.app.background_locator.pluggables.InitPluggable 18 | import rekab.app.background_locator.pluggables.Pluggable 19 | import rekab.app.background_locator.provider.* 20 | import java.util.HashMap 21 | 22 | class IsolateHolderService : MethodChannel.MethodCallHandler, LocationUpdateListener, Service() { 23 | companion object { 24 | @JvmStatic 25 | val ACTION_SHUTDOWN = "SHUTDOWN" 26 | 27 | @JvmStatic 28 | val ACTION_START = "START" 29 | 30 | @JvmStatic 31 | val ACTION_UPDATE_NOTIFICATION = "UPDATE_NOTIFICATION" 32 | 33 | @JvmStatic 34 | private val WAKELOCK_TAG = "IsolateHolderService::WAKE_LOCK" 35 | 36 | @JvmStatic 37 | var backgroundEngine: FlutterEngine? = null 38 | 39 | @JvmStatic 40 | private val notificationId = 1 41 | 42 | @JvmStatic 43 | var isServiceRunning = false 44 | } 45 | 46 | private var notificationChannelName = "Flutter Locator Plugin" 47 | private var notificationTitle = "Start Location Tracking" 48 | private var notificationMsg = "Track location in background" 49 | private var notificationBigMsg = "Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running." 50 | private var notificationIconColor = 0 51 | private var icon = 0 52 | private var wakeLockTime = 60 * 60 * 1000L // 1 hour default wake lock time 53 | private var locatorClient: BLLocationProvider? = null 54 | internal lateinit var backgroundChannel: MethodChannel 55 | internal lateinit var context: Context 56 | private var pluggables: ArrayList = ArrayList() 57 | 58 | override fun onBind(intent: Intent?): IBinder? { 59 | return null 60 | } 61 | 62 | override fun onCreate() { 63 | super.onCreate() 64 | startLocatorService(this) 65 | startForeground(notificationId, getNotification()) 66 | } 67 | 68 | private fun start() { 69 | (getSystemService(Context.POWER_SERVICE) as PowerManager).run { 70 | newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG).apply { 71 | setReferenceCounted(false) 72 | acquire(wakeLockTime) 73 | } 74 | } 75 | 76 | // Starting Service as foreground with a notification prevent service from closing 77 | val notification = getNotification() 78 | startForeground(notificationId, notification) 79 | 80 | pluggables.forEach { 81 | it.onServiceStart(context) 82 | } 83 | } 84 | 85 | private fun getNotification(): Notification { 86 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 87 | // Notification channel is available in Android O and up 88 | val channel = NotificationChannel(Keys.CHANNEL_ID, notificationChannelName, 89 | NotificationManager.IMPORTANCE_LOW) 90 | 91 | (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager) 92 | .createNotificationChannel(channel) 93 | } 94 | 95 | val intent = Intent(this, getMainActivityClass(this)) 96 | intent.action = Keys.NOTIFICATION_ACTION 97 | 98 | val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 99 | 1, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) 100 | 101 | return NotificationCompat.Builder(this, Keys.CHANNEL_ID) 102 | .setContentTitle(notificationTitle) 103 | .setContentText(notificationMsg) 104 | .setStyle(NotificationCompat.BigTextStyle() 105 | .bigText(notificationBigMsg)) 106 | .setSmallIcon(icon) 107 | .setColor(notificationIconColor) 108 | .setPriority(NotificationCompat.PRIORITY_HIGH) 109 | .setContentIntent(pendingIntent) 110 | .setOnlyAlertOnce(true) // so when data is updated don't make sound and alert in android 8.0+ 111 | .setOngoing(true) 112 | .build() 113 | } 114 | 115 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 116 | if (intent == null) { 117 | return super.onStartCommand(intent, flags, startId) 118 | } 119 | 120 | when { 121 | ACTION_SHUTDOWN == intent.action -> { 122 | isServiceRunning = false 123 | shutdownHolderService() 124 | } 125 | ACTION_START == intent.action -> { 126 | if (!isServiceRunning) { 127 | isServiceRunning = true 128 | startHolderService(intent) 129 | } 130 | } 131 | ACTION_UPDATE_NOTIFICATION == intent.action -> { 132 | if (isServiceRunning) { 133 | updateNotification(intent) 134 | } 135 | } 136 | } 137 | 138 | return START_STICKY 139 | } 140 | 141 | private fun startHolderService(intent: Intent) { 142 | notificationChannelName = intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME).toString() 143 | notificationTitle = intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE).toString() 144 | notificationMsg = intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG).toString() 145 | notificationBigMsg = intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG).toString() 146 | val iconNameDefault = "ic_launcher" 147 | var iconName = intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON) 148 | if (iconName == null || iconName.isEmpty()) { 149 | iconName = iconNameDefault 150 | } 151 | icon = resources.getIdentifier(iconName, "mipmap", packageName) 152 | notificationIconColor = intent.getLongExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR, 0).toInt() 153 | wakeLockTime = intent.getIntExtra(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME, 60) * 60 * 1000L 154 | 155 | locatorClient = getLocationClient(context) 156 | locatorClient?.requestLocationUpdates(getLocationRequest(intent)) 157 | 158 | // Fill pluggable list 159 | if( intent.hasExtra(Keys.SETTINGS_INIT_PLUGGABLE)) { 160 | pluggables.add(InitPluggable()) 161 | } 162 | 163 | if (intent.hasExtra(Keys.SETTINGS_DISPOSABLE_PLUGGABLE)) { 164 | pluggables.add(DisposePluggable()) 165 | } 166 | 167 | start() 168 | } 169 | 170 | private fun shutdownHolderService() { 171 | (getSystemService(Context.POWER_SERVICE) as PowerManager).run { 172 | newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG).apply { 173 | if (isHeld) { 174 | release() 175 | } 176 | } 177 | } 178 | 179 | locatorClient?.removeLocationUpdates() 180 | stopForeground(true) 181 | stopSelf() 182 | 183 | pluggables.forEach { 184 | it.onServiceDispose(context) 185 | } 186 | } 187 | 188 | private fun updateNotification(intent: Intent) { 189 | if (intent.hasExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE)) { 190 | notificationTitle = intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE).toString() 191 | } 192 | 193 | if (intent.hasExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG)) { 194 | notificationMsg = intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG).toString() 195 | } 196 | 197 | if (intent.hasExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG)) { 198 | notificationBigMsg = intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG).toString() 199 | } 200 | 201 | val notification = getNotification() 202 | val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 203 | notificationManager.notify(notificationId, notification) 204 | } 205 | 206 | private fun getMainActivityClass(context: Context): Class<*>? { 207 | val packageName = context.packageName 208 | val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName) 209 | val className = launchIntent?.component?.className ?: return null 210 | 211 | return try { 212 | Class.forName(className) 213 | } catch (e: ClassNotFoundException) { 214 | e.printStackTrace() 215 | null 216 | } 217 | } 218 | 219 | override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { 220 | when (call.method) { 221 | Keys.METHOD_SERVICE_INITIALIZED -> { 222 | isServiceRunning = true 223 | } 224 | else -> result.notImplemented() 225 | } 226 | 227 | result.success(null) 228 | } 229 | 230 | override fun onDestroy() { 231 | isServiceRunning = false 232 | super.onDestroy() 233 | } 234 | 235 | 236 | private fun getLocationClient(context: Context): BLLocationProvider { 237 | return when (PreferencesManager.getLocationClient(context)) { 238 | LocationClient.Google -> GoogleLocationProviderClient(context, this) 239 | LocationClient.Android -> AndroidLocationProviderClient(context, this) 240 | } 241 | } 242 | 243 | override fun onLocationUpdated(location: HashMap?) { 244 | FlutterInjector.instance().flutterLoader().ensureInitializationComplete(context, null) 245 | 246 | //https://github.com/flutter/plugins/pull/1641 247 | //https://github.com/flutter/flutter/issues/36059 248 | //https://github.com/flutter/plugins/pull/1641/commits/4358fbba3327f1fa75bc40df503ca5341fdbb77d 249 | // new version of flutter can not invoke method from background thread 250 | if (location != null) { 251 | val callback = PreferencesManager.getCallbackHandle(context, Keys.CALLBACK_HANDLE_KEY) as Long 252 | 253 | val result: HashMap = 254 | hashMapOf(Keys.ARG_CALLBACK to callback, 255 | Keys.ARG_LOCATION to location) 256 | 257 | sendLocationEvent(result) 258 | } 259 | } 260 | 261 | private fun sendLocationEvent(result: HashMap) { 262 | //https://github.com/flutter/plugins/pull/1641 263 | //https://github.com/flutter/flutter/issues/36059 264 | //https://github.com/flutter/plugins/pull/1641/commits/4358fbba3327f1fa75bc40df503ca5341fdbb77d 265 | // new version of flutter can not invoke method from background thread 266 | 267 | if (backgroundEngine != null) { 268 | val backgroundChannel = 269 | MethodChannel(backgroundEngine?.dartExecutor?.binaryMessenger, Keys.BACKGROUND_CHANNEL_ID) 270 | Handler(context.mainLooper) 271 | .post { 272 | Log.d("plugin", "sendLocationEvent $result") 273 | backgroundChannel.invokeMethod(Keys.BCM_SEND_LOCATION, result) 274 | } 275 | } 276 | } 277 | 278 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/rekab/app/background_locator/BackgroundLocatorPlugin.kt: -------------------------------------------------------------------------------- 1 | package rekab.app.background_locator 2 | 3 | import android.Manifest 4 | import android.annotation.SuppressLint 5 | import android.app.Activity 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.content.pm.PackageManager 9 | import android.os.Build 10 | import android.os.Handler 11 | import android.util.Log 12 | import androidx.core.content.ContextCompat 13 | import io.flutter.embedding.engine.plugins.FlutterPlugin 14 | import io.flutter.embedding.engine.plugins.activity.ActivityAware 15 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding 16 | import io.flutter.plugin.common.BinaryMessenger 17 | import io.flutter.plugin.common.MethodCall 18 | import io.flutter.plugin.common.MethodChannel 19 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 20 | import io.flutter.plugin.common.MethodChannel.Result 21 | import io.flutter.plugin.common.PluginRegistry 22 | import rekab.app.background_locator.pluggables.DisposePluggable 23 | import rekab.app.background_locator.pluggables.InitPluggable 24 | 25 | class BackgroundLocatorPlugin 26 | : MethodCallHandler, FlutterPlugin, PluginRegistry.NewIntentListener, ActivityAware { 27 | private var context: Context? = null 28 | private var activity: Activity? = null 29 | 30 | companion object { 31 | @JvmStatic 32 | private var channel: MethodChannel? = null 33 | 34 | @JvmStatic 35 | private fun sendResultWithDelay(context: Context, result: Result?, value: Boolean, delay: Long) { 36 | context.mainLooper.let { 37 | Handler(it).postDelayed({ 38 | result?.success(value) 39 | }, delay) 40 | } 41 | } 42 | 43 | @SuppressLint("MissingPermission") 44 | @JvmStatic 45 | private fun registerLocator(context: Context, 46 | args: Map, 47 | result: Result?) { 48 | if (IsolateHolderService.isServiceRunning) { 49 | // The service is running already 50 | Log.d("BackgroundLocatorPlugin", "Locator service is already running") 51 | result?.success(true) 52 | return 53 | } 54 | 55 | Log.d("BackgroundLocatorPlugin", 56 | "start locator with ${PreferencesManager.getLocationClient(context)} client") 57 | 58 | val callbackHandle = args[Keys.ARG_CALLBACK] as Long 59 | PreferencesManager.setCallbackHandle(context, Keys.CALLBACK_HANDLE_KEY, callbackHandle) 60 | 61 | val notificationCallback = args[Keys.ARG_NOTIFICATION_CALLBACK] as? Long 62 | PreferencesManager.setCallbackHandle(context, Keys.NOTIFICATION_CALLBACK_HANDLE_KEY, notificationCallback) 63 | 64 | // Call InitPluggable with initCallbackHandle 65 | (args[Keys.ARG_INIT_CALLBACK] as? Long)?.let { initCallbackHandle -> 66 | val initPluggable = InitPluggable() 67 | initPluggable.setCallback(context, initCallbackHandle) 68 | 69 | // Set init data if available 70 | (args[Keys.ARG_INIT_DATA_CALLBACK] as? Map<*, *>)?.let { initData -> 71 | initPluggable.setInitData(context, initData) 72 | } 73 | } 74 | 75 | // Call DisposePluggable with disposeCallbackHandle 76 | (args[Keys.ARG_DISPOSE_CALLBACK] as? Long)?.let { 77 | val disposePluggable = DisposePluggable() 78 | disposePluggable.setCallback(context, it) 79 | } 80 | 81 | val settings = args[Keys.ARG_SETTINGS] as Map<*, *> 82 | 83 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && 84 | context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) 85 | == PackageManager.PERMISSION_DENIED) { 86 | 87 | val msg = "'registerLocator' requires the ACCESS_FINE_LOCATION permission." 88 | result?.error(msg, null, null) 89 | return 90 | } 91 | 92 | startIsolateService(context, settings) 93 | 94 | // We need to know when the service binded exactly, there is some delay between starting a 95 | // service and it's binding 96 | // HELP WANTED: I couldn't find a better way to handle this, so any help or suggestion would be appreciated 97 | sendResultWithDelay(context, result, true, 1000) 98 | } 99 | 100 | @JvmStatic 101 | private fun startIsolateService(context: Context, settings: Map<*, *>) { 102 | val intent = Intent(context, IsolateHolderService::class.java) 103 | intent.action = IsolateHolderService.ACTION_START 104 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME, 105 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME] as String) 106 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE, 107 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE] as String) 108 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG, 109 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_MSG] as String) 110 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG, 111 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG] as String) 112 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON, 113 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON] as String) 114 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR, 115 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR] as Long) 116 | intent.putExtra(Keys.SETTINGS_INTERVAL, settings[Keys.SETTINGS_INTERVAL] as Int) 117 | intent.putExtra(Keys.SETTINGS_ACCURACY, settings[Keys.SETTINGS_ACCURACY] as Int) 118 | intent.putExtra(Keys.SETTINGS_DISTANCE_FILTER, settings[Keys.SETTINGS_DISTANCE_FILTER] as Double) 119 | 120 | if (settings.containsKey(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME)) { 121 | intent.putExtra(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME, 122 | settings[Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME] as Int) 123 | } 124 | 125 | if (PreferencesManager.getCallbackHandle(context, Keys.INIT_CALLBACK_HANDLE_KEY) != null) { 126 | intent.putExtra(Keys.SETTINGS_INIT_PLUGGABLE, true) 127 | } 128 | if (PreferencesManager.getCallbackHandle(context, Keys.DISPOSE_CALLBACK_HANDLE_KEY) != null) { 129 | intent.putExtra(Keys.SETTINGS_DISPOSABLE_PLUGGABLE, true) 130 | } 131 | 132 | ContextCompat.startForegroundService(context, intent) 133 | } 134 | 135 | @JvmStatic 136 | private fun stopIsolateService(context: Context) { 137 | val intent = Intent(context, IsolateHolderService::class.java) 138 | intent.action = IsolateHolderService.ACTION_SHUTDOWN 139 | ContextCompat.startForegroundService(context, intent) 140 | } 141 | 142 | @JvmStatic 143 | private fun initializeService(context: Context, args: Map) { 144 | val callbackHandle: Long = args[Keys.ARG_CALLBACK_DISPATCHER] as Long 145 | setCallbackDispatcherHandle(context, callbackHandle) 146 | } 147 | 148 | @JvmStatic 149 | private fun unRegisterPlugin(context: Context, result: Result?) { 150 | if (!IsolateHolderService.isServiceRunning) { 151 | // The service is not running 152 | Log.d("BackgroundLocatorPlugin", "Locator service is not running, nothing to stop") 153 | result?.success(true) 154 | return 155 | } 156 | 157 | stopIsolateService(context) 158 | 159 | // We need to know when the service detached exactly, there is some delay between stopping a 160 | // service and it's detachment 161 | // HELP WANTED: I couldn't find a better way to handle this, so any help or suggestion would be appreciated 162 | sendResultWithDelay(context, result, true, 1000) 163 | } 164 | 165 | @JvmStatic 166 | private fun isServiceRunning(result: Result?) { 167 | result?.success(IsolateHolderService.isServiceRunning) 168 | } 169 | 170 | @JvmStatic 171 | private fun updateNotificationText(context: Context, args: Map) { 172 | val intent = Intent(context, IsolateHolderService::class.java) 173 | intent.action = IsolateHolderService.ACTION_UPDATE_NOTIFICATION 174 | if (args.containsKey(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE)) { 175 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE, 176 | args[Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE] as String) 177 | } 178 | if (args.containsKey(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG)) { 179 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG, 180 | args[Keys.SETTINGS_ANDROID_NOTIFICATION_MSG] as String) 181 | } 182 | if (args.containsKey(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG)) { 183 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG, 184 | args[Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG] as String) 185 | } 186 | 187 | ContextCompat.startForegroundService(context, intent) 188 | } 189 | 190 | @JvmStatic 191 | private fun setCallbackDispatcherHandle(context: Context, handle: Long) { 192 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 193 | .edit() 194 | .putLong(Keys.CALLBACK_DISPATCHER_HANDLE_KEY, handle) 195 | .apply() 196 | } 197 | 198 | @JvmStatic 199 | fun registerAfterBoot(context: Context) { 200 | val args = PreferencesManager.getSettings(context) 201 | 202 | val plugin = BackgroundLocatorPlugin() 203 | plugin.context = context 204 | 205 | initializeService(context, args) 206 | 207 | val settings = args[Keys.ARG_SETTINGS] as Map<*, *> 208 | startIsolateService(context, settings) 209 | } 210 | } 211 | 212 | override fun onMethodCall(call: MethodCall, result: Result) { 213 | when (call.method) { 214 | Keys.METHOD_PLUGIN_INITIALIZE_SERVICE -> { 215 | val args: Map = call.arguments() 216 | 217 | // save callback dispatcher to use it when device reboots 218 | PreferencesManager.saveCallbackDispatcher(context!!, args) 219 | 220 | initializeService(context!!, args) 221 | result.success(true) 222 | } 223 | Keys.METHOD_PLUGIN_REGISTER_LOCATION_UPDATE -> { 224 | val args: Map = call.arguments() 225 | 226 | // save setting to use it when device reboots 227 | PreferencesManager.saveSettings(context!!, args) 228 | 229 | registerLocator(context!!, 230 | args, 231 | result) 232 | } 233 | Keys.METHOD_PLUGIN_UN_REGISTER_LOCATION_UPDATE -> { 234 | unRegisterPlugin(context!!, result) 235 | } 236 | Keys.METHOD_PLUGIN_IS_REGISTER_LOCATION_UPDATE -> isServiceRunning(result) 237 | Keys.METHOD_PLUGIN_IS_SERVICE_RUNNING -> isServiceRunning(result) 238 | Keys.METHOD_PLUGIN_UPDATE_NOTIFICATION -> { 239 | if (!IsolateHolderService.isServiceRunning) { 240 | return 241 | } 242 | 243 | val args: Map = call.arguments() 244 | updateNotificationText(context!!, args) 245 | result.success(true) 246 | } 247 | else -> result.notImplemented() 248 | } 249 | } 250 | 251 | override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { 252 | onAttachedToEngine(binding.applicationContext, binding.binaryMessenger) 253 | } 254 | 255 | override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { 256 | } 257 | 258 | private fun onAttachedToEngine(context: Context, messenger: BinaryMessenger) { 259 | val plugin = BackgroundLocatorPlugin() 260 | plugin.context = context 261 | 262 | channel = MethodChannel(messenger, Keys.CHANNEL_ID) 263 | channel?.setMethodCallHandler(plugin) 264 | } 265 | 266 | override fun onNewIntent(intent: Intent?): Boolean { 267 | if (intent?.action != Keys.NOTIFICATION_ACTION) { 268 | // this is not our notification 269 | return false 270 | } 271 | 272 | val notificationCallback = PreferencesManager.getCallbackHandle(activity!!, Keys.NOTIFICATION_CALLBACK_HANDLE_KEY) 273 | if (notificationCallback != null && IsolateHolderService.backgroundEngine != null) { 274 | val backgroundChannel = 275 | MethodChannel(IsolateHolderService.backgroundEngine?.dartExecutor?.binaryMessenger, Keys.BACKGROUND_CHANNEL_ID) 276 | activity?.mainLooper?.let { 277 | Handler(it) 278 | .post { 279 | backgroundChannel.invokeMethod(Keys.BCM_NOTIFICATION_CLICK, 280 | hashMapOf(Keys.ARG_NOTIFICATION_CALLBACK to notificationCallback)) 281 | } 282 | } 283 | } 284 | 285 | return true 286 | } 287 | 288 | override fun onDetachedFromActivity() { 289 | } 290 | 291 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { 292 | } 293 | 294 | override fun onAttachedToActivity(binding: ActivityPluginBinding) { 295 | activity = binding.activity 296 | binding.addOnNewIntentListener(this) 297 | } 298 | 299 | override fun onDetachedFromActivityForConfigChanges() { 300 | } 301 | 302 | 303 | } 304 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 5FAF19F85E027816144D7B4E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5372CD860C8B90D585E1F7CE /* Pods_Runner.framework */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 1C4857A6BC0D0258B774B80F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 37 | 4D2876AD2FA1EC20D292A6A2 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 38 | 5372CD860C8B90D585E1F7CE /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 40 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 41 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 42 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 43 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 44 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 46 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 48 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | F8332B7E520F2CC4E95F49CA /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 5FAF19F85E027816144D7B4E /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 75BD54FD2B96C8087E5CB617 /* Pods */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 4D2876AD2FA1EC20D292A6A2 /* Pods-Runner.debug.xcconfig */, 68 | F8332B7E520F2CC4E95F49CA /* Pods-Runner.release.xcconfig */, 69 | 1C4857A6BC0D0258B774B80F /* Pods-Runner.profile.xcconfig */, 70 | ); 71 | path = Pods; 72 | sourceTree = ""; 73 | }; 74 | 9740EEB11CF90186004384FC /* Flutter */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 78 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 79 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 80 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 81 | ); 82 | name = Flutter; 83 | sourceTree = ""; 84 | }; 85 | 97C146E51CF9000F007C117D = { 86 | isa = PBXGroup; 87 | children = ( 88 | 9740EEB11CF90186004384FC /* Flutter */, 89 | 97C146F01CF9000F007C117D /* Runner */, 90 | 97C146EF1CF9000F007C117D /* Products */, 91 | 75BD54FD2B96C8087E5CB617 /* Pods */, 92 | D3393787ECFB6E4BB5C05B2D /* Frameworks */, 93 | ); 94 | sourceTree = ""; 95 | }; 96 | 97C146EF1CF9000F007C117D /* Products */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 97C146EE1CF9000F007C117D /* Runner.app */, 100 | ); 101 | name = Products; 102 | sourceTree = ""; 103 | }; 104 | 97C146F01CF9000F007C117D /* Runner */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 108 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 109 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 110 | 97C147021CF9000F007C117D /* Info.plist */, 111 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 112 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 113 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 114 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 115 | ); 116 | path = Runner; 117 | sourceTree = ""; 118 | }; 119 | D3393787ECFB6E4BB5C05B2D /* Frameworks */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 5372CD860C8B90D585E1F7CE /* Pods_Runner.framework */, 123 | ); 124 | name = Frameworks; 125 | sourceTree = ""; 126 | }; 127 | /* End PBXGroup section */ 128 | 129 | /* Begin PBXNativeTarget section */ 130 | 97C146ED1CF9000F007C117D /* Runner */ = { 131 | isa = PBXNativeTarget; 132 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 133 | buildPhases = ( 134 | 3DFC2C986365D12AF8213180 /* [CP] Check Pods Manifest.lock */, 135 | 9740EEB61CF901F6004384FC /* Run Script */, 136 | 97C146EA1CF9000F007C117D /* Sources */, 137 | 97C146EB1CF9000F007C117D /* Frameworks */, 138 | 97C146EC1CF9000F007C117D /* Resources */, 139 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 140 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 141 | CA98270FF9B87F90EE113205 /* [CP] Embed Pods Frameworks */, 142 | ); 143 | buildRules = ( 144 | ); 145 | dependencies = ( 146 | ); 147 | name = Runner; 148 | productName = Runner; 149 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 150 | productType = "com.apple.product-type.application"; 151 | }; 152 | /* End PBXNativeTarget section */ 153 | 154 | /* Begin PBXProject section */ 155 | 97C146E61CF9000F007C117D /* Project object */ = { 156 | isa = PBXProject; 157 | attributes = { 158 | LastUpgradeCheck = 1300; 159 | ORGANIZATIONNAME = ""; 160 | TargetAttributes = { 161 | 97C146ED1CF9000F007C117D = { 162 | CreatedOnToolsVersion = 7.3.1; 163 | LastSwiftMigration = 1100; 164 | }; 165 | }; 166 | }; 167 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 168 | compatibilityVersion = "Xcode 9.3"; 169 | developmentRegion = en; 170 | hasScannedForEncodings = 0; 171 | knownRegions = ( 172 | en, 173 | Base, 174 | ); 175 | mainGroup = 97C146E51CF9000F007C117D; 176 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 177 | projectDirPath = ""; 178 | projectRoot = ""; 179 | targets = ( 180 | 97C146ED1CF9000F007C117D /* Runner */, 181 | ); 182 | }; 183 | /* End PBXProject section */ 184 | 185 | /* Begin PBXResourcesBuildPhase section */ 186 | 97C146EC1CF9000F007C117D /* Resources */ = { 187 | isa = PBXResourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 191 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 192 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 193 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXResourcesBuildPhase section */ 198 | 199 | /* Begin PBXShellScriptBuildPhase section */ 200 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 201 | isa = PBXShellScriptBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | ); 205 | inputPaths = ( 206 | ); 207 | name = "Thin Binary"; 208 | outputPaths = ( 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | shellPath = /bin/sh; 212 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 213 | }; 214 | 3DFC2C986365D12AF8213180 /* [CP] Check Pods Manifest.lock */ = { 215 | isa = PBXShellScriptBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | ); 219 | inputFileListPaths = ( 220 | ); 221 | inputPaths = ( 222 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 223 | "${PODS_ROOT}/Manifest.lock", 224 | ); 225 | name = "[CP] Check Pods Manifest.lock"; 226 | outputFileListPaths = ( 227 | ); 228 | outputPaths = ( 229 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | shellPath = /bin/sh; 233 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 234 | showEnvVarsInLog = 0; 235 | }; 236 | 9740EEB61CF901F6004384FC /* Run Script */ = { 237 | isa = PBXShellScriptBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | ); 241 | inputPaths = ( 242 | ); 243 | name = "Run Script"; 244 | outputPaths = ( 245 | ); 246 | runOnlyForDeploymentPostprocessing = 0; 247 | shellPath = /bin/sh; 248 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 249 | }; 250 | CA98270FF9B87F90EE113205 /* [CP] Embed Pods Frameworks */ = { 251 | isa = PBXShellScriptBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | ); 255 | inputFileListPaths = ( 256 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 257 | ); 258 | name = "[CP] Embed Pods Frameworks"; 259 | outputFileListPaths = ( 260 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | shellPath = /bin/sh; 264 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 265 | showEnvVarsInLog = 0; 266 | }; 267 | /* End PBXShellScriptBuildPhase section */ 268 | 269 | /* Begin PBXSourcesBuildPhase section */ 270 | 97C146EA1CF9000F007C117D /* Sources */ = { 271 | isa = PBXSourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 275 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | /* End PBXSourcesBuildPhase section */ 280 | 281 | /* Begin PBXVariantGroup section */ 282 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 283 | isa = PBXVariantGroup; 284 | children = ( 285 | 97C146FB1CF9000F007C117D /* Base */, 286 | ); 287 | name = Main.storyboard; 288 | sourceTree = ""; 289 | }; 290 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 291 | isa = PBXVariantGroup; 292 | children = ( 293 | 97C147001CF9000F007C117D /* Base */, 294 | ); 295 | name = LaunchScreen.storyboard; 296 | sourceTree = ""; 297 | }; 298 | /* End PBXVariantGroup section */ 299 | 300 | /* Begin XCBuildConfiguration section */ 301 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 302 | isa = XCBuildConfiguration; 303 | buildSettings = { 304 | ALWAYS_SEARCH_USER_PATHS = NO; 305 | CLANG_ANALYZER_NONNULL = YES; 306 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 307 | CLANG_CXX_LIBRARY = "libc++"; 308 | CLANG_ENABLE_MODULES = YES; 309 | CLANG_ENABLE_OBJC_ARC = YES; 310 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 311 | CLANG_WARN_BOOL_CONVERSION = YES; 312 | CLANG_WARN_COMMA = YES; 313 | CLANG_WARN_CONSTANT_CONVERSION = YES; 314 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 315 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 316 | CLANG_WARN_EMPTY_BODY = YES; 317 | CLANG_WARN_ENUM_CONVERSION = YES; 318 | CLANG_WARN_INFINITE_RECURSION = YES; 319 | CLANG_WARN_INT_CONVERSION = YES; 320 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 322 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 323 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 324 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 325 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 326 | CLANG_WARN_STRICT_PROTOTYPES = YES; 327 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 333 | ENABLE_NS_ASSERTIONS = NO; 334 | ENABLE_STRICT_OBJC_MSGSEND = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu99; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 344 | MTL_ENABLE_DEBUG_INFO = NO; 345 | SDKROOT = iphoneos; 346 | SUPPORTED_PLATFORMS = iphoneos; 347 | TARGETED_DEVICE_FAMILY = "1,2"; 348 | VALIDATE_PRODUCT = YES; 349 | }; 350 | name = Profile; 351 | }; 352 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 353 | isa = XCBuildConfiguration; 354 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 355 | buildSettings = { 356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 357 | CLANG_ENABLE_MODULES = YES; 358 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 359 | DEVELOPMENT_TEAM = Y9CYE9NBLT; 360 | ENABLE_BITCODE = NO; 361 | INFOPLIST_FILE = Runner/Info.plist; 362 | LD_RUNPATH_SEARCH_PATHS = ( 363 | "$(inherited)", 364 | "@executable_path/Frameworks", 365 | ); 366 | PRODUCT_BUNDLE_IDENTIFIER = rekab.app.example777; 367 | PRODUCT_NAME = "$(TARGET_NAME)"; 368 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 369 | SWIFT_VERSION = 5.0; 370 | VERSIONING_SYSTEM = "apple-generic"; 371 | }; 372 | name = Profile; 373 | }; 374 | 97C147031CF9000F007C117D /* Debug */ = { 375 | isa = XCBuildConfiguration; 376 | buildSettings = { 377 | ALWAYS_SEARCH_USER_PATHS = NO; 378 | CLANG_ANALYZER_NONNULL = YES; 379 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 380 | CLANG_CXX_LIBRARY = "libc++"; 381 | CLANG_ENABLE_MODULES = YES; 382 | CLANG_ENABLE_OBJC_ARC = YES; 383 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 384 | CLANG_WARN_BOOL_CONVERSION = YES; 385 | CLANG_WARN_COMMA = YES; 386 | CLANG_WARN_CONSTANT_CONVERSION = YES; 387 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 388 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 389 | CLANG_WARN_EMPTY_BODY = YES; 390 | CLANG_WARN_ENUM_CONVERSION = YES; 391 | CLANG_WARN_INFINITE_RECURSION = YES; 392 | CLANG_WARN_INT_CONVERSION = YES; 393 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 394 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 395 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 397 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 398 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 399 | CLANG_WARN_STRICT_PROTOTYPES = YES; 400 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 401 | CLANG_WARN_UNREACHABLE_CODE = YES; 402 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 403 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 404 | COPY_PHASE_STRIP = NO; 405 | DEBUG_INFORMATION_FORMAT = dwarf; 406 | ENABLE_STRICT_OBJC_MSGSEND = YES; 407 | ENABLE_TESTABILITY = YES; 408 | GCC_C_LANGUAGE_STANDARD = gnu99; 409 | GCC_DYNAMIC_NO_PIC = NO; 410 | GCC_NO_COMMON_BLOCKS = YES; 411 | GCC_OPTIMIZATION_LEVEL = 0; 412 | GCC_PREPROCESSOR_DEFINITIONS = ( 413 | "DEBUG=1", 414 | "$(inherited)", 415 | ); 416 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 417 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 418 | GCC_WARN_UNDECLARED_SELECTOR = YES; 419 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 420 | GCC_WARN_UNUSED_FUNCTION = YES; 421 | GCC_WARN_UNUSED_VARIABLE = YES; 422 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 423 | MTL_ENABLE_DEBUG_INFO = YES; 424 | ONLY_ACTIVE_ARCH = YES; 425 | SDKROOT = iphoneos; 426 | TARGETED_DEVICE_FAMILY = "1,2"; 427 | }; 428 | name = Debug; 429 | }; 430 | 97C147041CF9000F007C117D /* Release */ = { 431 | isa = XCBuildConfiguration; 432 | buildSettings = { 433 | ALWAYS_SEARCH_USER_PATHS = NO; 434 | CLANG_ANALYZER_NONNULL = YES; 435 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 436 | CLANG_CXX_LIBRARY = "libc++"; 437 | CLANG_ENABLE_MODULES = YES; 438 | CLANG_ENABLE_OBJC_ARC = YES; 439 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 440 | CLANG_WARN_BOOL_CONVERSION = YES; 441 | CLANG_WARN_COMMA = YES; 442 | CLANG_WARN_CONSTANT_CONVERSION = YES; 443 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 444 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 445 | CLANG_WARN_EMPTY_BODY = YES; 446 | CLANG_WARN_ENUM_CONVERSION = YES; 447 | CLANG_WARN_INFINITE_RECURSION = YES; 448 | CLANG_WARN_INT_CONVERSION = YES; 449 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 450 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 451 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 452 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 453 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 454 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 455 | CLANG_WARN_STRICT_PROTOTYPES = YES; 456 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 457 | CLANG_WARN_UNREACHABLE_CODE = YES; 458 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 459 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 460 | COPY_PHASE_STRIP = NO; 461 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 462 | ENABLE_NS_ASSERTIONS = NO; 463 | ENABLE_STRICT_OBJC_MSGSEND = YES; 464 | GCC_C_LANGUAGE_STANDARD = gnu99; 465 | GCC_NO_COMMON_BLOCKS = YES; 466 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 467 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 468 | GCC_WARN_UNDECLARED_SELECTOR = YES; 469 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 470 | GCC_WARN_UNUSED_FUNCTION = YES; 471 | GCC_WARN_UNUSED_VARIABLE = YES; 472 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 473 | MTL_ENABLE_DEBUG_INFO = NO; 474 | SDKROOT = iphoneos; 475 | SUPPORTED_PLATFORMS = iphoneos; 476 | SWIFT_COMPILATION_MODE = wholemodule; 477 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 478 | TARGETED_DEVICE_FAMILY = "1,2"; 479 | VALIDATE_PRODUCT = YES; 480 | }; 481 | name = Release; 482 | }; 483 | 97C147061CF9000F007C117D /* Debug */ = { 484 | isa = XCBuildConfiguration; 485 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 486 | buildSettings = { 487 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 488 | CLANG_ENABLE_MODULES = YES; 489 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 490 | DEVELOPMENT_TEAM = Y9CYE9NBLT; 491 | ENABLE_BITCODE = NO; 492 | INFOPLIST_FILE = Runner/Info.plist; 493 | LD_RUNPATH_SEARCH_PATHS = ( 494 | "$(inherited)", 495 | "@executable_path/Frameworks", 496 | ); 497 | PRODUCT_BUNDLE_IDENTIFIER = rekab.app.example777; 498 | PRODUCT_NAME = "$(TARGET_NAME)"; 499 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 500 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 501 | SWIFT_VERSION = 5.0; 502 | VERSIONING_SYSTEM = "apple-generic"; 503 | }; 504 | name = Debug; 505 | }; 506 | 97C147071CF9000F007C117D /* Release */ = { 507 | isa = XCBuildConfiguration; 508 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 509 | buildSettings = { 510 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 511 | CLANG_ENABLE_MODULES = YES; 512 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 513 | DEVELOPMENT_TEAM = Y9CYE9NBLT; 514 | ENABLE_BITCODE = NO; 515 | INFOPLIST_FILE = Runner/Info.plist; 516 | LD_RUNPATH_SEARCH_PATHS = ( 517 | "$(inherited)", 518 | "@executable_path/Frameworks", 519 | ); 520 | PRODUCT_BUNDLE_IDENTIFIER = rekab.app.example777; 521 | PRODUCT_NAME = "$(TARGET_NAME)"; 522 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 523 | SWIFT_VERSION = 5.0; 524 | VERSIONING_SYSTEM = "apple-generic"; 525 | }; 526 | name = Release; 527 | }; 528 | /* End XCBuildConfiguration section */ 529 | 530 | /* Begin XCConfigurationList section */ 531 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 532 | isa = XCConfigurationList; 533 | buildConfigurations = ( 534 | 97C147031CF9000F007C117D /* Debug */, 535 | 97C147041CF9000F007C117D /* Release */, 536 | 249021D3217E4FDB00AE95B9 /* Profile */, 537 | ); 538 | defaultConfigurationIsVisible = 0; 539 | defaultConfigurationName = Release; 540 | }; 541 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 542 | isa = XCConfigurationList; 543 | buildConfigurations = ( 544 | 97C147061CF9000F007C117D /* Debug */, 545 | 97C147071CF9000F007C117D /* Release */, 546 | 249021D4217E4FDB00AE95B9 /* Profile */, 547 | ); 548 | defaultConfigurationIsVisible = 0; 549 | defaultConfigurationName = Release; 550 | }; 551 | /* End XCConfigurationList section */ 552 | }; 553 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 554 | } 555 | --------------------------------------------------------------------------------