├── 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_2.podspec └── .gitignore ├── res └── values │ └── strings_en.arb ├── android ├── settings.gradle ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── yukams │ │ └── app │ │ └── background_locator_2 │ │ ├── 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 ├── .settings │ ├── org.eclipse.jdt.core.prefs │ └── org.eclipse.buildship.core.prefs ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .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 │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── 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 │ ├── build.gradle │ └── settings.gradle ├── README.md ├── lib │ ├── file_manager.dart │ ├── location_callback_handler.dart │ ├── location_service_repository.dart │ └── main.dart ├── test │ └── widget_test.dart ├── .gitignore ├── .metadata └── 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 ├── .github ├── workflows │ ├── build_pr.yml │ └── build.yml └── no-response.yml ├── pubspec.yaml ├── 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' 2 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/example/android/app/src/main/res/mipmap-xxxhdpi/ic_location.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/background_locator_fixed/master/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /android/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=23 3 | org.eclipse.jdt.core.compiler.compliance=23 4 | org.eclipse.jdt.core.compiler.source=23 5 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/background_locator_fixed/master/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/angebagui/background_locator_fixed/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/src/main/kotlin/yukams/app/background_locator_2/provider/LocationRequestOptions.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2.provider 2 | 3 | class LocationRequestOptions(val interval: Long, val accuracy: Int, val distanceFilter: Float) -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/rekab/app/background_locator_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity : FlutterActivity() -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip 6 | 7 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip 6 | -------------------------------------------------------------------------------- /android/src/main/kotlin/yukams/app/background_locator_2/provider/LocationUpdateListener.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2.provider 2 | 3 | import java.util.HashMap 4 | 5 | interface LocationUpdateListener { 6 | fun onLocationUpdated(location: HashMap?) 7 | } -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/src/main/kotlin/yukams/app/background_locator_2/provider/LocationClient.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2.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/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/yukams/app/background_locator_2/provider/BLLocationProvider.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2.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_2 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 | -------------------------------------------------------------------------------- /ios/Classes/pluggables/DisposePluggable.h: -------------------------------------------------------------------------------- 1 | // 2 | // DisposePluggable.h 3 | // background_locator_2 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/yukams/app/background_locator_2/pluggables/Pluggable.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2.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 | -------------------------------------------------------------------------------- /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_2'); 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_2 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/src/main/kotlin/yukams/app/background_locator_2/BootBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2 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/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_2 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 | -------------------------------------------------------------------------------- /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 | default: 17 | await BackgroundLocator.unRegisterLocationUpdate(); 18 | break; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.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/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments=--init-script /var/folders/qz/zhs0y0r13254_d3042b46tn80000gn/T/db3b08fc4a9ef609cb16b96b200fa13e563f396e9bb1ed0905fdab7bc3bc513b.gradle --init-script /var/folders/qz/zhs0y0r13254_d3042b46tn80000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=/opt/homebrew/Cellar/openjdk/23.0.1/libexec/openjdk.jdk/Contents/Home 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.20' 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 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /.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/background_locator_2.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_2' 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: background_locator_2 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: 2.10.2 4 | homepage: https://github.com/angebagui/background_locator_fixed/ 5 | issue_tracker: https://github.com/angebagui/background_locator_fixed/issues 6 | platforms: 7 | android: 8 | ios: 9 | 10 | 11 | environment: 12 | sdk: '>=3.3.3 <4.0.0' 13 | flutter: ">=1.12.0" 14 | dependencies: 15 | flutter: 16 | sdk: flutter 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | 22 | flutter: 23 | plugin: 24 | platforms: 25 | android: 26 | package: yukams.app.background_locator_2 27 | pluginClass: BackgroundLocatorPlugin 28 | ios: 29 | pluginClass: BackgroundLocatorPlugin 30 | -------------------------------------------------------------------------------- /ios/Classes/Preferences/PreferencesManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesManager.h 3 | // background_locator_2 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 | + (void)setStopWithTerminate:(BOOL) terminate; 25 | + (BOOL)isStopWithTerminate; 26 | 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /ios/Classes/pluggables/DisposePluggable.m: -------------------------------------------------------------------------------- 1 | // 2 | // DisposePluggable.m 3 | // background_locator_2 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/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | } 19 | 20 | plugins { 21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 22 | id "com.android.application" version "8.3.2" apply false 23 | id "org.jetbrains.kotlin.android" version "1.9.23" apply false 24 | } 25 | include ":app" -------------------------------------------------------------------------------- /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 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/rekab/app/background_locator_example/Application.kt: -------------------------------------------------------------------------------- 1 | package yukams.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 yukams.app.background_locator_2.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 | -------------------------------------------------------------------------------- /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_2_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 | -------------------------------------------------------------------------------- /ios/Classes/Helpers/MethodCallHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // MethodCallHelper.h 3 | // background_locator_2 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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 REKAB 2 | Copyright 2022 Yukams 3 | 4 | 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: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | 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. 9 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | background_locator_2-android 4 | Project android__ created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | 25 | 1735409714122 26 | 27 | 30 28 | 29 | org.eclipse.core.resources.regexFilterMatcher 30 | node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import background_locator_2 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 | -------------------------------------------------------------------------------- /example/lib/location_callback_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:background_locator_2/location_dto.dart'; 4 | 5 | import 'location_service_repository.dart'; 6 | 7 | @pragma('vm:entry-point') 8 | class LocationCallbackHandler { 9 | @pragma('vm:entry-point') 10 | static Future initCallback(Map params) async { 11 | LocationServiceRepository myLocationCallbackRepository = 12 | LocationServiceRepository(); 13 | await myLocationCallbackRepository.init(params); 14 | } 15 | 16 | @pragma('vm:entry-point') 17 | static Future disposeCallback() async { 18 | LocationServiceRepository myLocationCallbackRepository = 19 | LocationServiceRepository(); 20 | await myLocationCallbackRepository.dispose(); 21 | } 22 | 23 | @pragma('vm:entry-point') 24 | static Future callback(LocationDto locationDto) async { 25 | LocationServiceRepository myLocationCallbackRepository = 26 | LocationServiceRepository(); 27 | await myLocationCallbackRepository.callback(locationDto); 28 | } 29 | 30 | @pragma('vm:entry-point') 31 | static Future notificationCallback() async { 32 | print('***notificationCallback'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ios/Classes/pluggables/InitPluggable.m: -------------------------------------------------------------------------------- 1 | // 2 | // InitPluggable.m 3 | // background_locator_2 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 | -------------------------------------------------------------------------------- /lib/settings/ios_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:background_locator_2/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 | /// [stopWithTerminate] stops the location usage when the app is on background 13 | 14 | final bool showsBackgroundLocationIndicator; 15 | final bool stopWithTerminate; 16 | 17 | const IOSSettings({ 18 | LocationAccuracy accuracy = LocationAccuracy.NAVIGATION, 19 | double distanceFilter = 0, 20 | this.showsBackgroundLocationIndicator = false, 21 | this.stopWithTerminate = false, 22 | }) : super(accuracy: accuracy, distanceFilter: distanceFilter); //minutes 23 | 24 | Map toMap() { 25 | return { 26 | Keys.SETTINGS_ACCURACY: accuracy.value, 27 | Keys.SETTINGS_DISTANCE_FILTER: distanceFilter, 28 | Keys.SETTINGS_IOS_SHOWS_BACKGROUND_LOCATION_INDICATOR: 29 | showsBackgroundLocationIndicator, 30 | Keys.SETTINGS_IOS_STOP_WITH_TERMINATE: stopWithTerminate, 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/src/main/kotlin/yukams/app/background_locator_2/pluggables/DisposePluggable.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2.pluggables 2 | 3 | import android.content.Context 4 | import android.os.Handler 5 | import io.flutter.plugin.common.MethodChannel 6 | import yukams.app.background_locator_2.IsolateHolderService 7 | import yukams.app.background_locator_2.Keys 8 | import yukams.app.background_locator_2.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 | IsolateHolderService.getBinaryMessenger(context)?.let { binaryMessenger -> 18 | val backgroundChannel = MethodChannel(binaryMessenger, Keys.BACKGROUND_CHANNEL_ID) 19 | Handler(context.mainLooper) 20 | .post { 21 | backgroundChannel.invokeMethod( 22 | Keys.BCM_DISPOSE, 23 | hashMapOf(Keys.ARG_DISPOSE_CALLBACK to disposeCallback) 24 | ) 25 | } 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /ios/Classes/Utils/Util.m: -------------------------------------------------------------------------------- 1 | // 2 | // Util.m 3 | // background_locator_2 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, '11.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/build.gradle: -------------------------------------------------------------------------------- 1 | group = 'yukams.app.background_locator_2' 2 | version = '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.8.22' 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:8.1.2' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | namespace = "yukams.app.background_locator_2" 29 | compileSdk = 35 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_11 33 | targetCompatibility JavaVersion.VERSION_11 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = JavaVersion.VERSION_11 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | defaultConfig { 44 | minSdk = 23 45 | targetSdkVersion = 35 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | lintOptions { 49 | disable 'InvalidPackage' 50 | } 51 | } 52 | 53 | dependencies { 54 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 55 | implementation "com.google.android.gms:play-services-location:21.0.1" 56 | implementation 'com.google.code.gson:gson:2.8.6' 57 | implementation 'com.google.android.material:material:1.0.0' 58 | } 59 | -------------------------------------------------------------------------------- /android/src/main/kotlin/yukams/app/background_locator_2/provider/GoogleLocationProviderClient.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2.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 -------------------------------------------------------------------------------- /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. 5 | 6 | version: 7 | revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 8 | channel: master 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 17 | base_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 18 | - platform: android 19 | create_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 20 | base_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 21 | - platform: ios 22 | create_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 23 | base_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 24 | - platform: linux 25 | create_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 26 | base_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 27 | - platform: macos 28 | create_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 29 | base_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 30 | - platform: web 31 | create_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 32 | base_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 33 | - platform: windows 34 | create_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 35 | base_revision: 6c3a0e4d21da2478a58d8b00fbabfa29ef0df045 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This package is a V2 of the background_locator package, fixing it and making it work for the newest versions of Flutter. Please read the wiki in order to make this plugin work with flutter 3.x. 2 | 3 | # background_locator_2 ! [![pub package](https://img.shields.io/pub/v/background_locator_2.svg)](https://pub.dartlang.org/packages/background_locator_2) ![](https://img.shields.io/github/contributors/Yukams/background_locator_fixed) ![](https://img.shields.io/github/license/Yukams/background_locator_fixed) 4 | 5 | A Flutter plugin for getting location updates even when the app is killed. 6 | 7 | ![demo](https://raw.githubusercontent.com/RomanJos/background_locator/master/demo.gif) 8 | 9 | Refer to [wiki](https://github.com/Yukams/background_locator_fixed/wiki) page for install and setup instruction or jump to specific subject with below links: 10 | 11 | * [Installation](https://github.com/Yukams/background_locator_fixed/wiki/Installation) 12 | * [Setup](https://github.com/Yukams/background_locator_fixed/wiki/Setup) 13 | * [How to use](https://github.com/Yukams/background_locator_fixed/wiki/How-to-use) 14 | * [Use other plugins in callback](https://github.com/Yukams/background_locator_fixed/wiki/Use-other-plugins-in-callback) 15 | * [Stop on app terminate](https://github.com/Yukams/background_locator_fixed/wiki/Stop-on-app-terminate) 16 | * [LocationSettings options](https://github.com/Yukams/background_locator_fixed/wiki/LocationSettings-options) 17 | * [Restart service on device reboot (Android only)](https://github.com/Yukams/background_locator_fixed/wiki/Restart-service-on-device-reboot) 18 | 19 | ## License 20 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 21 | 22 | ## Contributor 23 | Thanks to all who contributed on this plugin to fix bugs and adding new feature, including: 24 | * [Rekab](https://github.com/rekabhq) (creator of V1) 25 | * [Gerardo Ibarra](https://github.com/gpibarra) 26 | * [RomanJos](https://github.com/RomanJos) 27 | * [Marcelo Henrique Neppel](https://github.com/marceloneppel) 28 | -------------------------------------------------------------------------------- /android/src/main/kotlin/yukams/app/background_locator_2/pluggables/InitPluggable.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2.pluggables 2 | 3 | import android.content.Context 4 | import android.os.Handler 5 | import io.flutter.plugin.common.MethodChannel 6 | import yukams.app.background_locator_2.IsolateHolderService 7 | import yukams.app.background_locator_2.Keys 8 | import yukams.app.background_locator_2.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 | IsolateHolderService.getBinaryMessenger(context)?.let { binaryMessenger -> 22 | val initialDataMap = PreferencesManager.getDataCallback(context, Keys.INIT_DATA_CALLBACK_KEY) 23 | val backgroundChannel = MethodChannel(binaryMessenger, Keys.BACKGROUND_CHANNEL_ID) 24 | Handler(context.mainLooper) 25 | .post { 26 | backgroundChannel.invokeMethod( 27 | Keys.BCM_INIT, 28 | hashMapOf( 29 | Keys.ARG_INIT_CALLBACK to initCallback, 30 | Keys.ARG_INIT_DATA_CALLBACK to initialDataMap 31 | ) 32 | ) 33 | } 34 | } 35 | } 36 | isInitCallbackCalled = true 37 | } 38 | } 39 | 40 | override fun onServiceDispose(context: Context) { 41 | isInitCallbackCalled = false 42 | } 43 | 44 | fun setInitData(context: Context, data: Map<*, *>) { 45 | PreferencesManager.setDataCallback(context, Keys.INIT_DATA_CALLBACK_KEY, data) 46 | } 47 | } -------------------------------------------------------------------------------- /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 | if (callback != null) { 23 | callback(location); 24 | } 25 | } else if (Keys.BCM_NOTIFICATION_CLICK == call.method) { 26 | final Map args = call.arguments; 27 | final Function? notificationCallback = 28 | PluginUtilities.getCallbackFromHandle(CallbackHandle.fromRawHandle( 29 | args[Keys.ARG_NOTIFICATION_CALLBACK])); 30 | if (notificationCallback != null) { 31 | notificationCallback(); 32 | } 33 | } else if (Keys.BCM_INIT == call.method) { 34 | final Map args = call.arguments; 35 | final Function? initCallback = PluginUtilities.getCallbackFromHandle( 36 | CallbackHandle.fromRawHandle(args[Keys.ARG_INIT_CALLBACK])); 37 | Map? data = args[Keys.ARG_INIT_DATA_CALLBACK]; 38 | if (initCallback != null) { 39 | initCallback(data); 40 | } 41 | } else if (Keys.BCM_DISPOSE == call.method) { 42 | final Map args = call.arguments; 43 | final Function? disposeCallback = PluginUtilities.getCallbackFromHandle( 44 | CallbackHandle.fromRawHandle(args[Keys.ARG_DISPOSE_CALLBACK])); 45 | if (disposeCallback != null) { 46 | disposeCallback(); 47 | } 48 | } 49 | }); 50 | _backgroundChannel.invokeMethod(Keys.METHOD_SERVICE_INITIALIZED); 51 | } 52 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | NSLocationAlwaysAndWhenInUseUsageDescription 28 | Location service needed. 29 | NSLocationAlwaysUsageDescription 30 | Location service needed. 31 | NSLocationWhenInUseUsageDescription 32 | Location service needed. 33 | UIBackgroundModes 34 | 35 | location 36 | 37 | UILaunchStoryboardName 38 | LaunchScreen 39 | UIMainStoryboardFile 40 | Main 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UISupportedInterfaceOrientations~ipad 48 | 49 | UIInterfaceOrientationPortrait 50 | UIInterfaceOrientationPortraitUpsideDown 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | UIViewControllerBasedStatusBarAppearance 55 | 56 | CADisableMinimumFrameDurationOnPhone 57 | 58 | UIApplicationSupportsIndirectInputEvents 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: background_locator_2_example 2 | description: Demonstrates how to use the background_locator_2 plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: '>=3.3.3 <4.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_2: 21 | path: ../ 22 | path_provider: ^2.0.8 23 | permission_handler: ^11.3.1 24 | win32: 5.9.0 25 | #location_permissions: ^4.0.1 26 | 27 | # For information on the generic Dart part of this file, see the 28 | # following page: https://dart.dev/tools/pub/pubspec 29 | 30 | # The following section is specific to Flutter. 31 | flutter: 32 | 33 | # The following line ensures that the Material Icons font is 34 | # included with your application, so that you can use the icons in 35 | # the material Icons class. 36 | uses-material-design: true 37 | 38 | # To add assets to your application, add an assets section, like this: 39 | # assets: 40 | # - images/a_dot_burr.jpeg 41 | # - images/a_dot_ham.jpeg 42 | 43 | # An image asset can refer to one or more resolution-specific "variants", see 44 | # https://flutter.dev/assets-and-images/#resolution-aware. 45 | 46 | # For details regarding adding assets from package dependencies, see 47 | # https://flutter.dev/assets-and-images/#from-packages 48 | 49 | # To add custom fonts to your application, add a fonts section here, 50 | # in this "flutter" section. Each entry in this list should have a 51 | # "family" key with the font family name, and a "fonts" key with a 52 | # list giving the asset and other descriptors for the font. For 53 | # example: 54 | # fonts: 55 | # - family: Schyler 56 | # fonts: 57 | # - asset: fonts/Schyler-Regular.ttf 58 | # - asset: fonts/Schyler-Italic.ttf 59 | # style: italic 60 | # - family: Trajan Pro 61 | # fonts: 62 | # - asset: fonts/TrajanPro.ttf 63 | # - asset: fonts/TrajanPro_Bold.ttf 64 | # weight: 700 65 | # 66 | # For details regarding fonts from package dependencies, 67 | # see https://flutter.dev/custom-fonts/#from-packages 68 | -------------------------------------------------------------------------------- /ios/Classes/Preferences/PreferencesManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesManager.m 3 | // background_locator_2 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 | + (void)setStopWithTerminate:(BOOL)terminate { 68 | [[NSUserDefaults standardUserDefaults] setBool:terminate forKey:kPrefStopWithTerminate]; 69 | } 70 | 71 | + (BOOL)isStopWithTerminate { 72 | return [[NSUserDefaults standardUserDefaults] boolForKey:kPrefStopWithTerminate]; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /ios/Classes/Helpers/MethodCallHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // MethodCallHelper.m 3 | // background_locator_2 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 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | // END: FlutterFire Configuration 4 | id "kotlin-android" 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | def localProperties = new Properties() 9 | def localPropertiesFile = rootProject.file('local.properties') 10 | if (localPropertiesFile.exists()) { 11 | localPropertiesFile.withReader('UTF-8') { reader -> 12 | localProperties.load(reader) 13 | } 14 | } 15 | 16 | def flutterRoot = localProperties.getProperty('flutter.sdk') 17 | if (flutterRoot == null) { 18 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 19 | } 20 | 21 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 22 | if (flutterVersionCode == null) { 23 | flutterVersionCode = '1' 24 | } 25 | 26 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 27 | if (flutterVersionName == null) { 28 | flutterVersionName = '1.0' 29 | } 30 | 31 | android { 32 | namespace = "yukams.app.background_locator_example" 33 | compileSdk = flutter.compileSdkVersion 34 | ndkVersion = flutter.ndkVersion 35 | 36 | sourceSets { 37 | main.java.srcDirs += 'src/main/kotlin' 38 | } 39 | 40 | kotlinOptions { 41 | jvmTarget = JavaVersion.VERSION_1_8 42 | } 43 | 44 | compileOptions { 45 | sourceCompatibility = JavaVersion.VERSION_1_8 46 | targetCompatibility = JavaVersion.VERSION_1_8 47 | } 48 | 49 | lintOptions { 50 | disable 'InvalidPackage' 51 | } 52 | 53 | defaultConfig { 54 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 55 | applicationId = "yukams.app.background_locator_example" 56 | minSdk = flutter.minSdkVersion 57 | minSdkVersion = 23 58 | targetSdk = flutter.targetSdkVersion 59 | versionCode = flutter.versionCode 60 | versionName = flutter.versionName 61 | } 62 | 63 | buildTypes { 64 | release { 65 | // TODO: Add your own signing config for the release build. 66 | // Signing with the debug keys for now, so `flutter run --release` works. 67 | signingConfig signingConfigs.debug 68 | } 69 | } 70 | } 71 | 72 | flutter { 73 | source '../..' 74 | } 75 | 76 | dependencies { 77 | testImplementation 'junit:junit:4.12' 78 | androidTestImplementation 'androidx.test:runner:1.3.0' 79 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 80 | } 81 | -------------------------------------------------------------------------------- /ios/Classes/Globals.h: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.h 3 | // background_locator_2 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 | FOUNDATION_EXPORT NSString *const kSettingsStopWithTerminate; 52 | 53 | FOUNDATION_EXPORT NSString *const kBCMSendLocation; 54 | FOUNDATION_EXPORT NSString *const kBCMInit; 55 | FOUNDATION_EXPORT NSString *const kBCMDispose; 56 | 57 | FOUNDATION_EXPORT NSString *const kPrefObservingRegion; 58 | FOUNDATION_EXPORT NSString *const kPrefServiceRunning; 59 | FOUNDATION_EXPORT NSString *const kPrefStopWithTerminate; 60 | 61 | @end 62 | 63 | NS_ASSUME_NONNULL_END 64 | -------------------------------------------------------------------------------- /android/src/main/kotlin/yukams/app/background_locator_2/provider/LocationParserUtil.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2.provider 2 | 3 | import android.location.Location 4 | import android.os.Build 5 | import com.google.android.gms.location.LocationResult 6 | import yukams.app.background_locator_2.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_2/keys.dart'; 5 | import 'package:background_locator_2/location_dto.dart'; 6 | import 'package:background_locator_2/settings/android_settings.dart'; 7 | import 'package:background_locator_2/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 | -------------------------------------------------------------------------------- /ios/Classes/Globals.m: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.m 3 | // background_locator_2 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.yukams/locator_plugin"; 19 | NSString *const kBackgroundChannelId = @"app.yukams/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 | NSString *const kSettingsStopWithTerminate = @"settings_ios_stopWithTerminate"; 49 | 50 | NSString *const kBCMSendLocation = @"BCM_SEND_LOCATION"; 51 | NSString *const kBCMInit = @"BCM_INIT"; 52 | NSString *const kBCMDispose = @"BCM_DISPOSE"; 53 | 54 | NSString *const kPrefObservingRegion = @"pref_observingRegion"; 55 | NSString *const kPrefServiceRunning = @"pref_serviceRunning"; 56 | NSString *const kPrefStopWithTerminate = @"pref_isStopWithTerminate"; 57 | 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /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_2/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.toJson()); 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)).toDouble(); 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 | -------------------------------------------------------------------------------- /lib/background_locator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:ui'; 3 | 4 | import 'package:background_locator_2/settings/android_settings.dart'; 5 | import 'package:background_locator_2/settings/ios_settings.dart'; 6 | import 'package:background_locator_2/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 WidgetsBinding? get _widgetsBinding => WidgetsBinding.instance; 26 | 27 | static Future registerLocationUpdate( 28 | void Function(LocationDto) callback, 29 | {void Function(Map)? initCallback, 30 | Map initDataCallback = const {}, 31 | void Function()? disposeCallback, 32 | bool autoStop = false, 33 | AndroidSettings androidSettings = const AndroidSettings(), 34 | IOSSettings iosSettings = const IOSSettings()}) async { 35 | if (autoStop) { 36 | _widgetsBinding!.addObserver(AutoStopHandler()); 37 | } 38 | 39 | final args = SettingsUtil.getArgumentsMap( 40 | callback: callback, 41 | initCallback: initCallback, 42 | initDataCallback: initDataCallback, 43 | disposeCallback: disposeCallback, 44 | androidSettings: androidSettings, 45 | iosSettings: iosSettings); 46 | 47 | await _channel.invokeMethod( 48 | Keys.METHOD_PLUGIN_REGISTER_LOCATION_UPDATE, args); 49 | } 50 | 51 | static Future unRegisterLocationUpdate() async { 52 | await _channel.invokeMethod(Keys.METHOD_PLUGIN_UN_REGISTER_LOCATION_UPDATE); 53 | } 54 | 55 | static Future isRegisterLocationUpdate() async { 56 | return (await _channel 57 | .invokeMethod(Keys.METHOD_PLUGIN_IS_REGISTER_LOCATION_UPDATE))!; 58 | } 59 | 60 | static Future isServiceRunning() async { 61 | return (await _channel 62 | .invokeMethod(Keys.METHOD_PLUGIN_IS_SERVICE_RUNNING))!; 63 | } 64 | 65 | static Future updateNotificationText( 66 | {String? title, String? msg, String? bigMsg}) async { 67 | final Map arg = {}; 68 | 69 | if (title != null) { 70 | arg[Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE] = title; 71 | } 72 | 73 | if (msg != null) { 74 | arg[Keys.SETTINGS_ANDROID_NOTIFICATION_MSG] = msg; 75 | } 76 | 77 | if (bigMsg != null) { 78 | arg[Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG] = bigMsg; 79 | } 80 | 81 | await _channel.invokeMethod(Keys.METHOD_PLUGIN_UPDATE_NOTIFICATION, arg); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 26 | 35 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/keys.dart: -------------------------------------------------------------------------------- 1 | class Keys { 2 | static const String CHANNEL_ID = 'app.yukams/locator_plugin'; 3 | static const String BACKGROUND_CHANNEL_ID = 4 | 'app.yukams/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 | static const String SETTINGS_IOS_STOP_WITH_TERMINATE = 63 | 'settings_ios_stopWithTerminate'; 64 | 65 | static const String BCM_SEND_LOCATION = 'BCM_SEND_LOCATION'; 66 | static const String BCM_NOTIFICATION_CLICK = 'BCM_NOTIFICATION_CLICK'; 67 | static const String BCM_INIT = 'BCM_INIT'; 68 | static const String BCM_DISPOSE = 'BCM_DISPOSE'; 69 | } 70 | -------------------------------------------------------------------------------- /android/src/main/kotlin/yukams/app/background_locator_2/provider/AndroidLocationProviderClient.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2.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_2/keys.dart'; 2 | import 'package:background_locator_2/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/yukams/app/background_locator_2/IsolateHolderExtension.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.pm.PackageManager 7 | import android.os.Build 8 | import android.util.Log 9 | import com.google.android.gms.location.LocationRequest 10 | import io.flutter.FlutterInjector 11 | import io.flutter.embedding.engine.FlutterEngine 12 | import io.flutter.embedding.engine.dart.DartExecutor 13 | import io.flutter.plugin.common.MethodChannel 14 | import io.flutter.view.FlutterCallbackInformation 15 | import yukams.app.background_locator_2.IsolateHolderService.Companion.isServiceInitialized 16 | import yukams.app.background_locator_2.provider.LocationRequestOptions 17 | import java.lang.RuntimeException 18 | import java.util.concurrent.atomic.AtomicBoolean 19 | 20 | internal fun IsolateHolderService.startLocatorService(context: Context) { 21 | 22 | val serviceStarted = AtomicBoolean(IsolateHolderService.isServiceRunning) 23 | // start synchronized block to prevent multiple service instant 24 | synchronized(serviceStarted) { 25 | this.context = context 26 | // resetting the background engine to avoid being stuck after an app crash 27 | IsolateHolderService.backgroundEngine?.destroy(); 28 | IsolateHolderService.backgroundEngine = null 29 | try { 30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && 31 | context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) 32 | == PackageManager.PERMISSION_GRANTED 33 | ) { 34 | // We need flutter engine to handle callback, so if it is not available we have to create a 35 | // Flutter engine without any view 36 | Log.e("IsolateHolderService", "startLocatorService: Start Flutter Engine") 37 | IsolateHolderService.backgroundEngine = FlutterEngine(context) 38 | 39 | val callbackHandle = context.getSharedPreferences( 40 | Keys.SHARED_PREFERENCES_KEY, 41 | Context.MODE_PRIVATE 42 | ) 43 | .getLong(Keys.CALLBACK_DISPATCHER_HANDLE_KEY, 0) 44 | val callbackInfo = 45 | FlutterCallbackInformation.lookupCallbackInformation(callbackHandle) 46 | 47 | if(callbackInfo == null) { 48 | Log.e("IsolateHolderExtension", "Fatal: failed to find callback"); 49 | return; 50 | } 51 | 52 | val args = DartExecutor.DartCallback( 53 | context.assets, 54 | FlutterInjector.instance().flutterLoader().findAppBundlePath(), 55 | callbackInfo 56 | ) 57 | IsolateHolderService.backgroundEngine?.dartExecutor?.executeDartCallback(args) 58 | isServiceInitialized = true 59 | Log.e("IsolateHolderExtension", "service initialized") 60 | } 61 | } catch (e: UnsatisfiedLinkError) { 62 | e.printStackTrace() 63 | } 64 | } 65 | 66 | IsolateHolderService.getBinaryMessenger(context)?.let { binaryMessenger -> 67 | backgroundChannel = 68 | MethodChannel( 69 | binaryMessenger, 70 | Keys.BACKGROUND_CHANNEL_ID 71 | ) 72 | try { 73 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && 74 | context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) 75 | == PackageManager.PERMISSION_GRANTED 76 | ) { 77 | backgroundChannel.setMethodCallHandler(this) 78 | } 79 | } catch (e: RuntimeException) { 80 | e.printStackTrace() 81 | } 82 | } 83 | } 84 | 85 | fun getLocationRequest(intent: Intent): LocationRequestOptions { 86 | val interval: Long = (intent.getIntExtra(Keys.SETTINGS_INTERVAL, 10) * 1000).toLong() 87 | val accuracyKey = intent.getIntExtra(Keys.SETTINGS_ACCURACY, 4) 88 | val accuracy = getAccuracy(accuracyKey) 89 | val distanceFilter = intent.getDoubleExtra(Keys.SETTINGS_DISTANCE_FILTER, 0.0) 90 | 91 | return LocationRequestOptions(interval, accuracy, distanceFilter.toFloat()) 92 | } 93 | 94 | fun getAccuracy(key: Int): Int { 95 | return when (key) { 96 | 0 -> LocationRequest.PRIORITY_NO_POWER 97 | 1 -> LocationRequest.PRIORITY_LOW_POWER 98 | 2 -> LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY 99 | 3 -> LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY 100 | 4 -> LocationRequest.PRIORITY_HIGH_ACCURACY 101 | else -> LocationRequest.PRIORITY_HIGH_ACCURACY 102 | } 103 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.0.6 2 | * Fixing iOS build 3 | * Fixing memory leak on Android 4 | * Fixing callbacks on older Android devices 5 | 6 | ## 2.0.6-dev.2 7 | * Fixing iOS build 8 | * Fixing memory leak on Android 9 | * Fixing callbacks on older Android devices 10 | 11 | ## 2.0.6-dev.1 12 | * Fix Type mismatch: inferred type is String? but Any was expected 13 | 14 | ## 2.0.5 15 | * Fixing demo gif 16 | 17 | ## 2.0.4 18 | * Merging 2.0.4-dev1 and 2.0.4-dev2 to the main branch 19 | 20 | ## 2.0.4-dev.2 21 | * Adding auto stop on app terminate on iOS 22 | * Fixing import on example test script 23 | 24 | ## 2.0.4-dev.1 25 | * Fixing iOS build 26 | * Replacing package:background_locator_2 by background_locator_2 27 | 28 | ## 2.0.3 29 | * Replacing background_locator by background_locator_2 **(BREAKING CHANGE)** 30 | * Replacing rekab by yukams 31 | 32 | ## 2.0.2 33 | * Adding fields to pubspec.yaml 34 | * Changing README.md to prepare it to be published on pub.dev 35 | * Changing LICENCE file 36 | 37 | ## 2.0.1 38 | * Reverting minSdk from 31 to 29 39 | 40 | ## 2.0.0 41 | * New fork 42 | * Support for Flutter 3.0 43 | * Support for NullSafety 44 | * Fix initCallback is now called first as expected 45 | * Fix callback not called 46 | * Fix example 47 | * Fix FLAG_IMMUTABLE error for SDK 23+ 48 | * Fix library not working after a phone reboot 49 | * Fix library not working after an app crash due to revoking permissions 50 | * Adding a bit more logs 51 | * Changing minSdk from 29 to 31 52 | 53 | ## 1.6.12 54 | * Fixes onStatusChanged crash; 55 | * Fixed issue #94; 56 | * Fix importing path_provider in ios example; 57 | * Fix issue #266; 58 | * Fix Android 12 location permission handling; 59 | * Add network location provider even gps location data not update; 60 | 61 | ## 1.6.6 62 | * Fix invoking method on flutter channel when engine is not ready; (#254) 63 | 64 | ## 1.6.5 65 | * Fix returning result for unRegisterPlugin method on Android; (#262) 66 | 67 | ## 1.6.4 68 | * Fix triggering location update when plugin is stopped; (#258) 69 | * Fix saving service start stop status; (#259) 70 | 71 | ## 1.6.3 72 | * Bug fixes; 73 | 74 | ## 1.6.2+1-beta 75 | * Bring back init and dispose callback; 76 | 77 | ## 1.6.1+1-beta 78 | * Fix crash on Android location client causing by change in status of location provider; 79 | 80 | ## 1.6.0+2-beta 81 | * Fix crash on start; 82 | 83 | ## 1.6.0+1-beta 84 | * Use new flutter engine; 85 | * Fix start stop bug which prevents correct state in plugin; 86 | 87 | ## 1.5.0+1 88 | * Add null safety support; 89 | 90 | ## 1.4.0+1 91 | * Set default value for autoStop; 92 | * Fix register and unregister futures never complete on Android; 93 | * Fix Doze problem for Android >= 10; 94 | 95 | ## 1.3.2+1 96 | * Fix compile error on sdk 30; 97 | * Fix app stop locating on android sdk 30 in background; 98 | 99 | ## 1.3.0+1 100 | * Add google location client as option; 101 | * Several bug fixes; 102 | 103 | ## 1.2.2+1 104 | * Add platform specific settings; 105 | * Add ability to update android notification; 106 | * Ability to showsBackgroundLocationIndicator on iOS; 107 | 108 | ## 1.1.13+1 109 | * add isServiceRunning method; 110 | 111 | ## 1.1.12+1 112 | * Added support for big text in Android notification; 113 | 114 | ## 1.1.11+1 115 | * Fix getCallbackHandle bug which caused some callbacks not getting executed; 116 | 117 | ## 1.1.10+1 118 | * Add region monitoring for iOS to get location info while app is terminated; 119 | * Minor iOS bug fix; 120 | * Add a way to use 3rd party plugins while app is terminated in iOS; 121 | 122 | ## 1.1.7+1 123 | * Add notification icon color; 124 | * Add isMocked property on location model; 125 | * Add channel name property on location dto; 126 | 127 | ## 1.1.5+1 128 | * Fix crash in onStartCommand caused by null intent on Android; 129 | * Fix getting several unwanted position on iOS; 130 | 131 | ## 1.1.3+1 132 | * Add possibility to restart locator service after reboot; 133 | * Fix triggering android notification callback with wrong notification; 134 | 135 | ## 1.1.2+2 136 | * Fix optional android notification callback. 137 | 138 | ## 1.1.2+1 139 | * Fix accessing other plugins when app is terminated. 140 | 141 | ## 1.1.1+1 142 | * ‌Fix Callback is not triggered in iOS. 143 | 144 | ## 1.1.0+1 145 | * Add callback for android notification. 146 | 147 | ## 1.0.1+2 148 | * Fix crash on detach. 149 | 150 | ## 1.0.1+1 151 | * Add isRegistered method. 152 | * Bug fixes. 153 | 154 | ## 1.0.0+1 155 | * Add auto stop feature. 156 | * Update flutter plugin library to version 2. 157 | 158 | ## 0.0.4-beta 159 | * Add parameter to setting to change android wakelock time. 160 | * Prevent service from registering twice. 161 | 162 | ## 0.0.3-beta 163 | Change where location access requested. 164 | 165 | ## 0.0.2-beta 166 | 167 | * Improvements. 168 | 169 | ## 0.0.1-beta 170 | 171 | * initial release. 172 | -------------------------------------------------------------------------------- /android/src/main/kotlin/yukams/app/background_locator_2/Keys.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2 2 | 3 | class Keys { 4 | companion object { 5 | @JvmStatic 6 | val SHARED_PREFERENCES_KEY:String = "SHARED_PREFERENCES_KEY" 7 | 8 | @JvmStatic 9 | val CALLBACK_DISPATCHER_HANDLE_KEY:String = "CALLBACK_DISPATCHER_HANDLE_KEY" 10 | 11 | @JvmStatic 12 | val CALLBACK_HANDLE_KEY:String = "CALLBACK_HANDLE_KEY" 13 | 14 | @JvmStatic 15 | val NOTIFICATION_CALLBACK_HANDLE_KEY:String = "NOTIFICATION_CALLBACK_HANDLE_KEY" 16 | 17 | @JvmStatic 18 | val INIT_CALLBACK_HANDLE_KEY:String = "INIT_CALLBACK_HANDLE_KEY" 19 | 20 | @JvmStatic 21 | val INIT_DATA_CALLBACK_KEY:String = "INIT_DATA_CALLBACK_KEY" 22 | 23 | @JvmStatic 24 | val DISPOSE_CALLBACK_HANDLE_KEY:String = "DISPOSE_CALLBACK_HANDLE_KEY" 25 | 26 | @JvmStatic 27 | val CHANNEL_ID:String = "app.yukams/locator_plugin" 28 | 29 | @JvmStatic 30 | val BACKGROUND_CHANNEL_ID:String = "app.yukams/locator_plugin_background" 31 | 32 | @JvmStatic 33 | val METHOD_SERVICE_INITIALIZED:String = "LocatorService.initialized" 34 | 35 | @JvmStatic 36 | val METHOD_PLUGIN_INITIALIZE_SERVICE:String = "LocatorPlugin.initializeService" 37 | 38 | @JvmStatic 39 | val METHOD_PLUGIN_REGISTER_LOCATION_UPDATE:String = "LocatorPlugin.registerLocationUpdate" 40 | 41 | @JvmStatic 42 | val METHOD_PLUGIN_UN_REGISTER_LOCATION_UPDATE:String = "LocatorPlugin.unRegisterLocationUpdate" 43 | 44 | @JvmStatic 45 | val METHOD_PLUGIN_IS_REGISTER_LOCATION_UPDATE:String = "LocatorPlugin.isRegisterLocationUpdate" 46 | 47 | @JvmStatic 48 | val METHOD_PLUGIN_IS_SERVICE_RUNNING:String = "LocatorPlugin.isServiceRunning" 49 | 50 | @JvmStatic 51 | val METHOD_PLUGIN_UPDATE_NOTIFICATION:String = "LocatorPlugin.updateNotification" 52 | 53 | @JvmStatic 54 | val ARG_INIT_CALLBACK:String = "initCallback" 55 | 56 | @JvmStatic 57 | val ARG_INIT_DATA_CALLBACK:String = "initDataCallback" 58 | 59 | @JvmStatic 60 | val ARG_DISPOSE_CALLBACK:String = "disposeCallback" 61 | 62 | @JvmStatic 63 | val ARG_IS_MOCKED:String = "is_mocked" 64 | 65 | @JvmStatic 66 | val ARG_LATITUDE:String = "latitude" 67 | 68 | @JvmStatic 69 | val ARG_LONGITUDE:String = "longitude" 70 | 71 | @JvmStatic 72 | val ARG_ACCURACY:String = "accuracy" 73 | 74 | @JvmStatic 75 | val ARG_ALTITUDE:String = "altitude" 76 | 77 | @JvmStatic 78 | val ARG_SPEED:String = "speed" 79 | 80 | @JvmStatic 81 | val ARG_SPEED_ACCURACY:String = "speed_accuracy" 82 | 83 | @JvmStatic 84 | val ARG_HEADING:String = "heading" 85 | 86 | @JvmStatic 87 | val ARG_TIME:String = "time" 88 | 89 | @JvmStatic 90 | val ARG_PROVIDER:String = "provider" 91 | 92 | @JvmStatic 93 | val ARG_CALLBACK:String = "callback" 94 | 95 | @JvmStatic 96 | val ARG_NOTIFICATION_CALLBACK:String = "notificationCallback" 97 | 98 | @JvmStatic 99 | val ARG_LOCATION:String = "location" 100 | 101 | @JvmStatic 102 | val ARG_SETTINGS:String = "settings" 103 | 104 | @JvmStatic 105 | val ARG_CALLBACK_DISPATCHER:String = "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 = "yukams.app.background_locator_2.notification" 161 | } 162 | } -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:isolate'; 3 | import 'dart:ui'; 4 | 5 | import 'package:background_locator_2/background_locator.dart'; 6 | import 'package:background_locator_2/location_dto.dart'; 7 | import 'package:background_locator_2/settings/android_settings.dart'; 8 | import 'package:background_locator_2/settings/ios_settings.dart'; 9 | import 'package:background_locator_2/settings/locator_settings.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:permission_handler/permission_handler.dart'; 12 | 13 | import 'file_manager.dart'; 14 | import 'location_callback_handler.dart'; 15 | import 'location_service_repository.dart'; 16 | 17 | void main() => runApp(MyApp()); 18 | 19 | class MyApp extends StatefulWidget { 20 | @override 21 | _MyAppState createState() => _MyAppState(); 22 | } 23 | 24 | class _MyAppState extends State { 25 | ReceivePort port = ReceivePort(); 26 | 27 | String logStr = ''; 28 | bool isRunning = false; 29 | LocationDto? lastLocation; 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | 35 | if (IsolateNameServer.lookupPortByName( 36 | LocationServiceRepository.isolateName) != 37 | null) { 38 | IsolateNameServer.removePortNameMapping( 39 | LocationServiceRepository.isolateName); 40 | } 41 | 42 | IsolateNameServer.registerPortWithName( 43 | port.sendPort, LocationServiceRepository.isolateName); 44 | 45 | port.listen( 46 | (dynamic data) async { 47 | await updateUI(data); 48 | }, 49 | ); 50 | initPlatformState(); 51 | } 52 | 53 | @override 54 | void dispose() { 55 | super.dispose(); 56 | } 57 | 58 | Future updateUI(dynamic data) async { 59 | final log = await FileManager.readLogFile(); 60 | 61 | LocationDto? locationDto = (data != null) ? LocationDto.fromJson(data) : null; 62 | await _updateNotificationText(locationDto); 63 | 64 | setState(() { 65 | if (data != null) { 66 | lastLocation = locationDto; 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 | final access = await Permission.location.status; 185 | 186 | if(access.isDenied || await Permission.location.isRestricted){ 187 | final permission = await Permission.location.request(); 188 | if(permission.isGranted){ 189 | return true; 190 | }else{ 191 | return false; 192 | } 193 | }else{ 194 | return true; 195 | } 196 | } 197 | 198 | Future _startLocator() async{ 199 | Map data = {'countInit': 1}; 200 | return await BackgroundLocator.registerLocationUpdate(LocationCallbackHandler.callback, 201 | initCallback: LocationCallbackHandler.initCallback, 202 | initDataCallback: data, 203 | disposeCallback: LocationCallbackHandler.disposeCallback, 204 | iosSettings: IOSSettings( 205 | accuracy: LocationAccuracy.NAVIGATION, 206 | distanceFilter: 0, 207 | stopWithTerminate: true 208 | ), 209 | autoStop: false, 210 | androidSettings: AndroidSettings( 211 | accuracy: LocationAccuracy.NAVIGATION, 212 | interval: 5, 213 | distanceFilter: 0, 214 | client: LocationClient.google, 215 | androidNotificationSettings: AndroidNotificationSettings( 216 | notificationChannelName: 'Location tracking', 217 | notificationTitle: 'Start Location Tracking', 218 | notificationMsg: 'Track location in background', 219 | notificationBigMsg: 220 | '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.', 221 | notificationIconColor: Colors.grey, 222 | notificationTapCallback: 223 | LocationCallbackHandler.notificationCallback))); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /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/yukams/app/background_locator_2/PreferencesManager.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2 2 | 3 | import android.content.Context 4 | import com.google.gson.Gson 5 | import com.google.gson.reflect.TypeToken 6 | import yukams.app.background_locator_2.provider.LocationClient 7 | 8 | class PreferencesManager { 9 | companion object { 10 | private const val PREF_NAME = "background_locator_2" 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 | val callback = map[Keys.ARG_CALLBACK] as Number 29 | sharedPreferences.edit() 30 | .putLong(Keys.ARG_CALLBACK, 31 | callback.toLong()) 32 | .apply() 33 | 34 | if (map[Keys.ARG_NOTIFICATION_CALLBACK] as? Long != null) { 35 | sharedPreferences.edit() 36 | .putLong(Keys.ARG_NOTIFICATION_CALLBACK, 37 | map[Keys.ARG_NOTIFICATION_CALLBACK] as Long) 38 | .apply() 39 | } 40 | 41 | val settings = map[Keys.ARG_SETTINGS] as Map<*, *> 42 | 43 | sharedPreferences.edit() 44 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME, 45 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME] as String) 46 | .apply() 47 | 48 | sharedPreferences.edit() 49 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE, 50 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE] as String) 51 | .apply() 52 | 53 | sharedPreferences.edit() 54 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG, 55 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_MSG] as String) 56 | .apply() 57 | 58 | sharedPreferences.edit() 59 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG, 60 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG] as String) 61 | .apply() 62 | 63 | sharedPreferences.edit() 64 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON, 65 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON] as String) 66 | .apply() 67 | 68 | sharedPreferences.edit() 69 | .putLong(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR, 70 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR] as Long) 71 | .apply() 72 | 73 | sharedPreferences.edit() 74 | .putInt(Keys.SETTINGS_INTERVAL, 75 | settings[Keys.SETTINGS_INTERVAL] as Int) 76 | .apply() 77 | 78 | sharedPreferences.edit() 79 | .putInt(Keys.SETTINGS_ACCURACY, 80 | settings[Keys.SETTINGS_ACCURACY] as Int) 81 | .apply() 82 | 83 | sharedPreferences.edit() 84 | .putFloat(Keys.SETTINGS_DISTANCE_FILTER, 85 | (settings[Keys.SETTINGS_DISTANCE_FILTER] as Double).toFloat()) 86 | .apply() 87 | 88 | if (settings.containsKey(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME)) { 89 | sharedPreferences.edit() 90 | .putInt(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME, 91 | settings[Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME] as Int) 92 | .apply() 93 | } 94 | 95 | sharedPreferences.edit() 96 | .putInt(Keys.SETTINGS_ANDROID_LOCATION_CLIENT, 97 | settings[Keys.SETTINGS_ANDROID_LOCATION_CLIENT] as Int) 98 | .apply() 99 | } 100 | 101 | @JvmStatic 102 | fun getSettings(context: Context): Map { 103 | val sharedPreferences = 104 | context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) 105 | 106 | val result = HashMap() 107 | 108 | result[Keys.ARG_CALLBACK_DISPATCHER] = sharedPreferences.getLong(Keys.ARG_CALLBACK_DISPATCHER, 0) 109 | result[Keys.ARG_CALLBACK] = sharedPreferences.getLong(Keys.ARG_CALLBACK, 0) 110 | 111 | if (sharedPreferences.contains(Keys.ARG_NOTIFICATION_CALLBACK)) { 112 | result[Keys.ARG_NOTIFICATION_CALLBACK] = 113 | sharedPreferences.getLong(Keys.ARG_NOTIFICATION_CALLBACK, 0) 114 | } 115 | 116 | val settings = HashMap() 117 | 118 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME] = 119 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME, "") 120 | 121 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE] = 122 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE, "") 123 | 124 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_MSG] = 125 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG, "") 126 | 127 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG] = 128 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG, "") 129 | 130 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON] = 131 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON, "") 132 | 133 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR] = 134 | sharedPreferences.getLong(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR, 0) 135 | 136 | settings[Keys.SETTINGS_INTERVAL] = 137 | sharedPreferences.getInt(Keys.SETTINGS_INTERVAL, 0) 138 | 139 | settings[Keys.SETTINGS_ACCURACY] = 140 | sharedPreferences.getInt(Keys.SETTINGS_ACCURACY, 0) 141 | 142 | settings[Keys.SETTINGS_DISTANCE_FILTER] = 143 | sharedPreferences.getFloat(Keys.SETTINGS_DISTANCE_FILTER, 0f).toDouble() 144 | 145 | if (sharedPreferences.contains(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME)) { 146 | settings[Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME] = sharedPreferences.getInt(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME, 0) 147 | } 148 | 149 | settings[Keys.SETTINGS_ANDROID_LOCATION_CLIENT] = 150 | sharedPreferences.getInt(Keys.SETTINGS_ANDROID_LOCATION_CLIENT, 0) 151 | 152 | result[Keys.ARG_SETTINGS] = settings 153 | return result 154 | } 155 | 156 | @JvmStatic 157 | fun getLocationClient(context: Context): LocationClient { 158 | val sharedPreferences = 159 | context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) 160 | val client = sharedPreferences.getInt(Keys.SETTINGS_ANDROID_LOCATION_CLIENT, 0) 161 | return LocationClient.fromInt(client) ?: LocationClient.Google 162 | } 163 | 164 | @JvmStatic 165 | fun setCallbackHandle(context: Context, key: String, handle: Long?) { 166 | if (handle == null) { 167 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 168 | .edit() 169 | .remove(key) 170 | .apply() 171 | return 172 | } 173 | 174 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 175 | .edit() 176 | .putLong(key, handle) 177 | .apply() 178 | } 179 | 180 | @JvmStatic 181 | fun setDataCallback(context: Context, key: String, data: Map<*, *>?) { 182 | if (data == null) { 183 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 184 | .edit() 185 | .remove(key) 186 | .apply() 187 | return 188 | } 189 | val dataStr = Gson().toJson(data) 190 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 191 | .edit() 192 | .putString(key, dataStr) 193 | .apply() 194 | } 195 | 196 | @JvmStatic 197 | fun getCallbackHandle(context: Context, key: String): Long? { 198 | val sharedPreferences = context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 199 | if (sharedPreferences.contains(key)) return sharedPreferences.getLong(key, 0L) 200 | return null 201 | } 202 | 203 | @JvmStatic 204 | fun getDataCallback(context: Context, key: String): Map<*, *> { 205 | val initialDataStr = context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 206 | .getString(key, null) 207 | val type = object : TypeToken>() {}.type 208 | return Gson().fromJson(initialDataStr, type) 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /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 | if([PreferencesManager isStopWithTerminate]){ 84 | [self removeLocator]; 85 | } 86 | } 87 | 88 | - (void) observeRegionForLocation:(CLLocation *)location { 89 | double distanceFilter = [PreferencesManager getDistanceFilter]; 90 | CLRegion* region = [[CLCircularRegion alloc] initWithCenter:location.coordinate 91 | radius:distanceFilter 92 | identifier:@"region"]; 93 | region.notifyOnEntry = false; 94 | region.notifyOnExit = true; 95 | [_locationManager startMonitoringForRegion:region]; 96 | } 97 | 98 | - (void) prepareLocationMap:(CLLocation*) location { 99 | _lastLocation = location; 100 | NSDictionary* locationMap = [Util getLocationMap:location]; 101 | 102 | [self sendLocationEvent:locationMap]; 103 | } 104 | 105 | #pragma mark LocationManagerDelegate Methods 106 | - (void)locationManager:(CLLocationManager *)manager 107 | didUpdateLocations:(NSArray *)locations { 108 | if (locations.count > 0) { 109 | CLLocation* location = [locations objectAtIndex:0]; 110 | [self prepareLocationMap: location]; 111 | if([PreferencesManager isObservingRegion]) { 112 | [self observeRegionForLocation: location]; 113 | [_locationManager stopUpdatingLocation]; 114 | } 115 | } 116 | } 117 | 118 | - (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region { 119 | [_locationManager stopMonitoringForRegion:region]; 120 | [_locationManager startUpdatingLocation]; 121 | } 122 | 123 | #pragma mark LocatorPlugin Methods 124 | - (void) sendLocationEvent: (NSDictionary*)location { 125 | NSString *isolateId = [_headlessRunner isolateId]; 126 | if (_callbackChannel == nil || isolateId == nil) { 127 | return; 128 | } 129 | 130 | NSDictionary *map = @{ 131 | kArgCallback : @([PreferencesManager getCallbackHandle:kCallbackKey]), 132 | kArgLocation: location 133 | }; 134 | [_callbackChannel invokeMethod:kBCMSendLocation arguments:map]; 135 | } 136 | 137 | - (instancetype)init:(NSObject *)registrar { 138 | self = [super init]; 139 | 140 | _headlessRunner = [[FlutterEngine alloc] initWithName:@"LocatorIsolate" project:nil allowHeadlessExecution:YES]; 141 | _registrar = registrar; 142 | [self prepareLocationManager]; 143 | 144 | _mainChannel = [FlutterMethodChannel methodChannelWithName:kChannelId 145 | binaryMessenger:[registrar messenger]]; 146 | [registrar addMethodCallDelegate:self channel:_mainChannel]; 147 | 148 | _callbackChannel = 149 | [FlutterMethodChannel methodChannelWithName:kBackgroundChannelId 150 | binaryMessenger:[_headlessRunner binaryMessenger] ]; 151 | return self; 152 | } 153 | 154 | - (void) prepareLocationManager { 155 | _locationManager = [[CLLocationManager alloc] init]; 156 | [_locationManager setDelegate:self]; 157 | _locationManager.pausesLocationUpdatesAutomatically = NO; 158 | } 159 | 160 | #pragma mark MethodCallHelperDelegate 161 | 162 | - (void)startLocatorService:(int64_t)handle { 163 | [PreferencesManager setCallbackDispatcherHandle:handle]; 164 | FlutterCallbackInformation *info = [FlutterCallbackCache lookupCallbackInformation:handle]; 165 | NSAssert(info != nil, @"failed to find callback"); 166 | 167 | NSString *entrypoint = info.callbackName; 168 | NSString *uri = info.callbackLibraryPath; 169 | [_headlessRunner runWithEntrypoint:entrypoint libraryURI:uri]; 170 | NSAssert(registerPlugins != nil, @"failed to set registerPlugins"); 171 | 172 | // Once our headless runner has been started, we need to register the application's plugins 173 | // with the runner in order for them to work on the background isolate. `registerPlugins` is 174 | // a callback set from AppDelegate.m in the main application. This callback should register 175 | // all relevant plugins (excluding those which require UI). 176 | static dispatch_once_t onceToken; 177 | dispatch_once(&onceToken, ^{ 178 | registerPlugins(_headlessRunner); 179 | }); 180 | [_registrar addMethodCallDelegate:self channel:_callbackChannel]; 181 | } 182 | 183 | - (void)registerLocator:(int64_t)callback 184 | initCallback:(int64_t)initCallback 185 | initialDataDictionary:(NSDictionary*)initialDataDictionary 186 | disposeCallback:(int64_t)disposeCallback 187 | settings: (NSDictionary*)settings { 188 | [self->_locationManager requestAlwaysAuthorization]; 189 | 190 | long accuracyKey = [[settings objectForKey:kSettingsAccuracy] longValue]; 191 | CLLocationAccuracy accuracy = [Util getAccuracy:accuracyKey]; 192 | double distanceFilter= [[settings objectForKey:kSettingsDistanceFilter] doubleValue]; 193 | bool showsBackgroundLocationIndicator=[[settings objectForKey:kSettingsShowsBackgroundLocationIndicator] boolValue]; 194 | bool stopWithTerminate=[[settings objectForKey:kSettingsStopWithTerminate] boolValue]; 195 | 196 | _locationManager.desiredAccuracy = accuracy; 197 | _locationManager.distanceFilter = distanceFilter; 198 | 199 | if (@available(iOS 11.0, *)) { 200 | _locationManager.showsBackgroundLocationIndicator = showsBackgroundLocationIndicator; 201 | } 202 | 203 | if (@available(iOS 9.0, *)) { 204 | _locationManager.allowsBackgroundLocationUpdates = YES; 205 | } 206 | 207 | [PreferencesManager saveDistanceFilter:distanceFilter]; 208 | [PreferencesManager setStopWithTerminate:stopWithTerminate]; 209 | 210 | [PreferencesManager setCallbackHandle:callback key:kCallbackKey]; 211 | 212 | InitPluggable *initPluggable = [[InitPluggable alloc] init]; 213 | [initPluggable setCallback:initCallback]; 214 | [initPluggable onServiceStart:initialDataDictionary]; 215 | 216 | DisposePluggable *disposePluggable = [[DisposePluggable alloc] init]; 217 | [disposePluggable setCallback:disposeCallback]; 218 | 219 | [_locationManager startUpdatingLocation]; 220 | [_locationManager startMonitoringSignificantLocationChanges]; 221 | } 222 | 223 | - (void)removeLocator { 224 | if (_locationManager == nil) { 225 | return; 226 | } 227 | 228 | @synchronized (self) { 229 | [_locationManager stopUpdatingLocation]; 230 | 231 | if (@available(iOS 9.0, *)) { 232 | _locationManager.allowsBackgroundLocationUpdates = NO; 233 | } 234 | 235 | [_locationManager stopMonitoringSignificantLocationChanges]; 236 | 237 | for (CLRegion* region in [_locationManager monitoredRegions]) { 238 | [_locationManager stopMonitoringForRegion:region]; 239 | } 240 | } 241 | 242 | DisposePluggable *disposePluggable = [[DisposePluggable alloc] init]; 243 | [disposePluggable onServiceDispose]; 244 | } 245 | 246 | - (void) setServiceRunning:(BOOL) value { 247 | @synchronized(self) { 248 | [PreferencesManager setServiceRunning:value]; 249 | } 250 | } 251 | 252 | - (BOOL)isServiceRunning{ 253 | return [PreferencesManager isServiceRunning]; 254 | } 255 | 256 | - (BOOL)isStopWithTerminate{ 257 | return [PreferencesManager isStopWithTerminate]; 258 | } 259 | 260 | @end 261 | -------------------------------------------------------------------------------- /android/src/main/kotlin/yukams/app/background_locator_2/IsolateHolderService.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2 2 | 3 | import android.app.* 4 | import android.Manifest 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.os.Build 8 | import android.os.Handler 9 | import android.os.IBinder 10 | import android.os.PowerManager 11 | import android.util.Log 12 | import androidx.core.app.NotificationCompat 13 | import android.content.pm.PackageManager 14 | import io.flutter.FlutterInjector 15 | import io.flutter.embedding.engine.FlutterEngine 16 | import io.flutter.plugin.common.BinaryMessenger 17 | import io.flutter.plugin.common.MethodCall 18 | import io.flutter.plugin.common.MethodChannel 19 | import yukams.app.background_locator_2.pluggables.DisposePluggable 20 | import yukams.app.background_locator_2.pluggables.InitPluggable 21 | import yukams.app.background_locator_2.pluggables.Pluggable 22 | import yukams.app.background_locator_2.provider.* 23 | import java.util.HashMap 24 | import androidx.core.app.ActivityCompat 25 | 26 | class IsolateHolderService : MethodChannel.MethodCallHandler, LocationUpdateListener, Service() { 27 | companion object { 28 | @JvmStatic 29 | val ACTION_SHUTDOWN = "SHUTDOWN" 30 | 31 | @JvmStatic 32 | val ACTION_START = "START" 33 | 34 | @JvmStatic 35 | val ACTION_UPDATE_NOTIFICATION = "UPDATE_NOTIFICATION" 36 | 37 | @JvmStatic 38 | private val WAKELOCK_TAG = "IsolateHolderService::WAKE_LOCK" 39 | 40 | @JvmStatic 41 | var backgroundEngine: FlutterEngine? = null 42 | 43 | @JvmStatic 44 | private val notificationId = 1 45 | 46 | @JvmStatic 47 | var isServiceRunning = false 48 | 49 | @JvmStatic 50 | var isServiceInitialized = false 51 | 52 | fun getBinaryMessenger(context: Context?): BinaryMessenger? { 53 | val messenger = backgroundEngine?.dartExecutor?.binaryMessenger 54 | return messenger 55 | ?: if (context != null) { 56 | backgroundEngine = FlutterEngine(context) 57 | backgroundEngine?.dartExecutor?.binaryMessenger 58 | }else{ 59 | messenger 60 | } 61 | } 62 | } 63 | 64 | private var notificationChannelName = "Flutter Locator Plugin" 65 | private var notificationTitle = "Start Location Tracking" 66 | private var notificationMsg = "Track location in background" 67 | private var notificationBigMsg = 68 | "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." 69 | private var notificationIconColor = 0 70 | private var icon = 0 71 | private var wakeLockTime = 60 * 60 * 1000L // 1 hour default wake lock time 72 | private var locatorClient: BLLocationProvider? = null 73 | internal lateinit var backgroundChannel: MethodChannel 74 | internal var context: Context? = null 75 | private var pluggables: ArrayList = ArrayList() 76 | 77 | override fun onBind(intent: Intent?): IBinder? { 78 | return null 79 | } 80 | 81 | override fun onCreate() { 82 | super.onCreate() 83 | startLocatorService(this) 84 | startForeground(notificationId, getNotification()) 85 | } 86 | 87 | private fun start() { 88 | (getSystemService(Context.POWER_SERVICE) as PowerManager).run { 89 | newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG).apply { 90 | setReferenceCounted(false) 91 | acquire(wakeLockTime) 92 | } 93 | } 94 | 95 | // Starting Service as foreground with a notification prevent service from closing 96 | val notification = getNotification() 97 | startForeground(notificationId, notification) 98 | 99 | pluggables.forEach { 100 | context?.let { it1 -> it.onServiceStart(it1) } 101 | } 102 | } 103 | 104 | private fun getNotification(): Notification { 105 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 106 | // Notification channel is available in Android O and up 107 | val channel = NotificationChannel( 108 | Keys.CHANNEL_ID, notificationChannelName, 109 | NotificationManager.IMPORTANCE_LOW 110 | ) 111 | 112 | (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager) 113 | .createNotificationChannel(channel) 114 | } 115 | 116 | val intent = Intent(this, getMainActivityClass(this)) 117 | intent.action = Keys.NOTIFICATION_ACTION 118 | 119 | val pendingIntent: PendingIntent = PendingIntent.getActivity( 120 | this, 121 | 1, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT 122 | ) 123 | 124 | return NotificationCompat.Builder(this, Keys.CHANNEL_ID) 125 | .setContentTitle(notificationTitle) 126 | .setContentText(notificationMsg) 127 | .setStyle( 128 | NotificationCompat.BigTextStyle() 129 | .bigText(notificationBigMsg) 130 | ) 131 | .setSmallIcon(icon) 132 | .setColor(notificationIconColor) 133 | .setPriority(NotificationCompat.PRIORITY_HIGH) 134 | .setContentIntent(pendingIntent) 135 | .setOnlyAlertOnce(true) // so when data is updated don't make sound and alert in android 8.0+ 136 | .setOngoing(true) 137 | .build() 138 | } 139 | 140 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 141 | Log.e("IsolateHolderService", "onStartCommand => intent.action : ${intent?.action}") 142 | if(intent == null) { 143 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED 144 | || ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { 145 | Log.e("IsolateHolderService", "app has crashed, stopping it") 146 | stopSelf() 147 | } 148 | else { 149 | return super.onStartCommand(intent, flags, startId) 150 | } 151 | } 152 | 153 | when { 154 | ACTION_SHUTDOWN == intent?.action -> { 155 | isServiceRunning = false 156 | shutdownHolderService() 157 | } 158 | ACTION_START == intent?.action -> { 159 | if (isServiceRunning) { 160 | isServiceRunning = false 161 | shutdownHolderService() 162 | } 163 | 164 | if (!isServiceRunning) { 165 | isServiceRunning = true 166 | startHolderService(intent) 167 | } 168 | } 169 | ACTION_UPDATE_NOTIFICATION == intent?.action -> { 170 | if (isServiceRunning) { 171 | updateNotification(intent) 172 | } 173 | } 174 | } 175 | 176 | return START_STICKY 177 | } 178 | 179 | private fun startHolderService(intent: Intent) { 180 | Log.e("IsolateHolderService", "startHolderService") 181 | notificationChannelName = 182 | intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME).toString() 183 | notificationTitle = 184 | intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE).toString() 185 | notificationMsg = intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG).toString() 186 | notificationBigMsg = 187 | intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG).toString() 188 | val iconNameDefault = "ic_launcher" 189 | var iconName = intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON) 190 | if (iconName == null || iconName.isEmpty()) { 191 | iconName = iconNameDefault 192 | } 193 | icon = resources.getIdentifier(iconName, "mipmap", packageName) 194 | notificationIconColor = 195 | intent.getLongExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR, 0).toInt() 196 | wakeLockTime = intent.getIntExtra(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME, 60) * 60 * 1000L 197 | 198 | locatorClient = context?.let { getLocationClient(it) } 199 | locatorClient?.requestLocationUpdates(getLocationRequest(intent)) 200 | 201 | // Fill pluggable list 202 | if (intent.hasExtra(Keys.SETTINGS_INIT_PLUGGABLE)) { 203 | pluggables.add(InitPluggable()) 204 | } 205 | 206 | if (intent.hasExtra(Keys.SETTINGS_DISPOSABLE_PLUGGABLE)) { 207 | pluggables.add(DisposePluggable()) 208 | } 209 | 210 | start() 211 | } 212 | 213 | private fun shutdownHolderService() { 214 | Log.e("IsolateHolderService", "shutdownHolderService") 215 | (getSystemService(Context.POWER_SERVICE) as PowerManager).run { 216 | newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG).apply { 217 | if (isHeld) { 218 | release() 219 | } 220 | } 221 | } 222 | 223 | locatorClient?.removeLocationUpdates() 224 | stopForeground(true) 225 | stopSelf() 226 | 227 | pluggables.forEach { 228 | context?.let { it1 -> it.onServiceDispose(it1) } 229 | } 230 | } 231 | 232 | private fun updateNotification(intent: Intent) { 233 | Log.e("IsolateHolderService", "updateNotification") 234 | if (intent.hasExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE)) { 235 | notificationTitle = 236 | intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE).toString() 237 | } 238 | 239 | if (intent.hasExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG)) { 240 | notificationMsg = 241 | intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG).toString() 242 | } 243 | 244 | if (intent.hasExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG)) { 245 | notificationBigMsg = 246 | intent.getStringExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG).toString() 247 | } 248 | 249 | val notification = getNotification() 250 | val notificationManager = 251 | getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 252 | notificationManager.notify(notificationId, notification) 253 | } 254 | 255 | private fun getMainActivityClass(context: Context): Class<*>? { 256 | val packageName = context.packageName 257 | val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName) 258 | val className = launchIntent?.component?.className ?: return null 259 | 260 | return try { 261 | Class.forName(className) 262 | } catch (e: ClassNotFoundException) { 263 | e.printStackTrace() 264 | null 265 | } 266 | } 267 | 268 | override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { 269 | try { 270 | when (call.method) { 271 | Keys.METHOD_SERVICE_INITIALIZED -> { 272 | isServiceRunning = true 273 | } 274 | else -> result.notImplemented() 275 | } 276 | 277 | result.success(null) 278 | } catch (e: Exception) { 279 | 280 | } 281 | } 282 | 283 | override fun onDestroy() { 284 | isServiceRunning = false 285 | super.onDestroy() 286 | } 287 | 288 | 289 | private fun getLocationClient(context: Context): BLLocationProvider { 290 | return when (PreferencesManager.getLocationClient(context)) { 291 | LocationClient.Google -> GoogleLocationProviderClient(context, this) 292 | LocationClient.Android -> AndroidLocationProviderClient(context, this) 293 | } 294 | } 295 | 296 | override fun onLocationUpdated(location: HashMap?) { 297 | try { 298 | context?.let { 299 | FlutterInjector.instance().flutterLoader().ensureInitializationComplete( 300 | it, null 301 | ) 302 | } 303 | 304 | //https://github.com/flutter/plugins/pull/1641 305 | //https://github.com/flutter/flutter/issues/36059 306 | //https://github.com/flutter/plugins/pull/1641/commits/4358fbba3327f1fa75bc40df503ca5341fdbb77d 307 | // new version of flutter can not invoke method from background thread 308 | if (location != null) { 309 | val callback = 310 | context?.let { 311 | PreferencesManager.getCallbackHandle( 312 | it, 313 | Keys.CALLBACK_HANDLE_KEY 314 | ) 315 | } as Long 316 | 317 | val result: HashMap? = 318 | hashMapOf( 319 | Keys.ARG_CALLBACK to callback, 320 | Keys.ARG_LOCATION to location 321 | ) 322 | 323 | sendLocationEvent(result) 324 | } 325 | } catch (e: Exception) { 326 | 327 | } 328 | } 329 | 330 | private fun sendLocationEvent(result: HashMap?) { 331 | //https://github.com/flutter/plugins/pull/1641 332 | //https://github.com/flutter/flutter/issues/36059 333 | //https://github.com/flutter/plugins/pull/1641/commits/4358fbba3327f1fa75bc40df503ca5341fdbb77d 334 | // new version of flutter can not invoke method from background thread 335 | 336 | if (backgroundEngine != null) { 337 | context?.let { 338 | val backgroundChannel = 339 | MethodChannel( 340 | getBinaryMessenger(it)!!, 341 | Keys.BACKGROUND_CHANNEL_ID 342 | ) 343 | Handler(it.mainLooper) 344 | .post { 345 | Log.d("plugin", "sendLocationEvent $result") 346 | backgroundChannel.invokeMethod(Keys.BCM_SEND_LOCATION, result) 347 | } 348 | } 349 | } 350 | } 351 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/yukams/app/background_locator_2/BackgroundLocatorPlugin.kt: -------------------------------------------------------------------------------- 1 | package yukams.app.background_locator_2 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 yukams.app.background_locator_2.pluggables.DisposePluggable 23 | import yukams.app.background_locator_2.pluggables.InitPluggable 24 | 25 | class BackgroundLocatorPlugin 26 | : MethodCallHandler, FlutterPlugin, PluginRegistry.NewIntentListener, ActivityAware { 27 | 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 | Log.e("BackgroundLocatorPlugin", "startIsolateService") 103 | val intent = Intent(context, IsolateHolderService::class.java) 104 | intent.action = IsolateHolderService.ACTION_START 105 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME, 106 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME] as? String) 107 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE, 108 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE] as? String) 109 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG, 110 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_MSG] as? String) 111 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG, 112 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG] as? String) 113 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON, 114 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON] as? String) 115 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR, 116 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR] as? Long) 117 | intent.putExtra(Keys.SETTINGS_INTERVAL, settings[Keys.SETTINGS_INTERVAL] as? Int) 118 | intent.putExtra(Keys.SETTINGS_ACCURACY, settings[Keys.SETTINGS_ACCURACY] as? Int) 119 | intent.putExtra(Keys.SETTINGS_DISTANCE_FILTER, settings[Keys.SETTINGS_DISTANCE_FILTER] as? Double) 120 | 121 | if (settings.containsKey(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME)) { 122 | intent.putExtra(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME, 123 | settings[Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME] as Int) 124 | } 125 | 126 | if (PreferencesManager.getCallbackHandle(context, Keys.INIT_CALLBACK_HANDLE_KEY) != null) { 127 | intent.putExtra(Keys.SETTINGS_INIT_PLUGGABLE, true) 128 | } 129 | if (PreferencesManager.getCallbackHandle(context, Keys.DISPOSE_CALLBACK_HANDLE_KEY) != null) { 130 | intent.putExtra(Keys.SETTINGS_DISPOSABLE_PLUGGABLE, true) 131 | } 132 | 133 | ContextCompat.startForegroundService(context, intent) 134 | } 135 | 136 | @JvmStatic 137 | private fun stopIsolateService(context: Context) { 138 | val intent = Intent(context, IsolateHolderService::class.java) 139 | intent.action = IsolateHolderService.ACTION_SHUTDOWN 140 | Log.d("BackgroundLocatorPlugin", "stopIsolateService => Shutting down locator plugin") 141 | ContextCompat.startForegroundService(context, intent) 142 | } 143 | 144 | @JvmStatic 145 | private fun initializeService(context: Context, args: Map) { 146 | val callbackHandle: Long = args[Keys.ARG_CALLBACK_DISPATCHER] as Long 147 | setCallbackDispatcherHandle(context, callbackHandle) 148 | } 149 | 150 | @JvmStatic 151 | private fun unRegisterPlugin(context: Context, result: Result?) { 152 | if (!IsolateHolderService.isServiceRunning) { 153 | // The service is not running 154 | Log.d("BackgroundLocatorPlugin", "Locator service is not running, nothing to stop") 155 | result?.success(true) 156 | return 157 | } 158 | 159 | stopIsolateService(context) 160 | 161 | // We need to know when the service detached exactly, there is some delay between stopping a 162 | // service and it's detachment 163 | // HELP WANTED: I couldn't find a better way to handle this, so any help or suggestion would be appreciated 164 | sendResultWithDelay(context, result, true, 1000) 165 | } 166 | 167 | @JvmStatic 168 | private fun isServiceRunning(result: Result?) { 169 | result?.success(IsolateHolderService.isServiceRunning) 170 | } 171 | 172 | @JvmStatic 173 | private fun updateNotificationText(context: Context, args: Map) { 174 | val intent = Intent(context, IsolateHolderService::class.java) 175 | intent.action = IsolateHolderService.ACTION_UPDATE_NOTIFICATION 176 | if (args.containsKey(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE)) { 177 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE, 178 | args[Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE] as String) 179 | } 180 | if (args.containsKey(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG)) { 181 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG, 182 | args[Keys.SETTINGS_ANDROID_NOTIFICATION_MSG] as String) 183 | } 184 | if (args.containsKey(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG)) { 185 | intent.putExtra(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG, 186 | args[Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG] as String) 187 | } 188 | 189 | ContextCompat.startForegroundService(context, intent) 190 | } 191 | 192 | @JvmStatic 193 | private fun setCallbackDispatcherHandle(context: Context, handle: Long) { 194 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) 195 | .edit() 196 | .putLong(Keys.CALLBACK_DISPATCHER_HANDLE_KEY, handle) 197 | .apply() 198 | } 199 | 200 | @JvmStatic 201 | fun registerAfterBoot(context: Context) { 202 | val args = PreferencesManager.getSettings(context) 203 | 204 | val plugin = BackgroundLocatorPlugin() 205 | plugin.context = context 206 | 207 | initializeService(context, args) 208 | 209 | val settings = args[Keys.ARG_SETTINGS] as Map<*, *> 210 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && 211 | context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) 212 | == PackageManager.PERMISSION_GRANTED 213 | ) { 214 | startIsolateService(context, settings) 215 | } 216 | } 217 | } 218 | 219 | override fun onMethodCall(call: MethodCall, result: Result) { 220 | when (call.method) { 221 | Keys.METHOD_PLUGIN_INITIALIZE_SERVICE -> { 222 | val args: Map? = call.arguments() 223 | 224 | // save callback dispatcher to use it when device reboots 225 | PreferencesManager.saveCallbackDispatcher(context!! , args!!) 226 | 227 | 228 | 229 | 230 | initializeService(context!!, args) 231 | result.success(true) 232 | } 233 | Keys.METHOD_PLUGIN_REGISTER_LOCATION_UPDATE -> { 234 | val args: Map? = call.arguments() 235 | 236 | // save setting to use it when device reboots 237 | 238 | PreferencesManager.saveSettings(context!!, args!!) 239 | 240 | registerLocator(context!!, 241 | args, 242 | result) 243 | } 244 | Keys.METHOD_PLUGIN_UN_REGISTER_LOCATION_UPDATE -> { 245 | unRegisterPlugin(context!!, result) 246 | } 247 | Keys.METHOD_PLUGIN_IS_REGISTER_LOCATION_UPDATE -> isServiceRunning(result) 248 | Keys.METHOD_PLUGIN_IS_SERVICE_RUNNING -> isServiceRunning(result) 249 | Keys.METHOD_PLUGIN_UPDATE_NOTIFICATION -> { 250 | if (!IsolateHolderService.isServiceRunning) { 251 | return 252 | } 253 | 254 | val args: Map? = call.arguments() 255 | 256 | updateNotificationText(context!!, args!!) 257 | 258 | 259 | result.success(true) 260 | } 261 | else -> result.notImplemented() 262 | } 263 | } 264 | 265 | override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { 266 | onAttachedToEngine(binding.applicationContext, binding.binaryMessenger) 267 | } 268 | 269 | override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { 270 | } 271 | 272 | private fun onAttachedToEngine(context: Context, messenger: BinaryMessenger) { 273 | val plugin = BackgroundLocatorPlugin() 274 | plugin.context = context 275 | 276 | channel = MethodChannel(messenger, Keys.CHANNEL_ID) 277 | channel?.setMethodCallHandler(plugin) 278 | } 279 | 280 | override fun onNewIntent(intent: Intent): Boolean { 281 | if (intent.action != Keys.NOTIFICATION_ACTION) { 282 | // this is not our notification 283 | return false 284 | } 285 | 286 | IsolateHolderService.getBinaryMessenger(context)?.let { binaryMessenger -> 287 | val notificationCallback = 288 | PreferencesManager.getCallbackHandle( 289 | activity!!, 290 | Keys.NOTIFICATION_CALLBACK_HANDLE_KEY 291 | ) 292 | if (notificationCallback != null && IsolateHolderService.backgroundEngine != null) { 293 | val backgroundChannel = 294 | MethodChannel( 295 | binaryMessenger, 296 | Keys.BACKGROUND_CHANNEL_ID 297 | ) 298 | activity?.mainLooper?.let { 299 | Handler(it) 300 | .post { 301 | backgroundChannel.invokeMethod( 302 | Keys.BCM_NOTIFICATION_CLICK, 303 | hashMapOf(Keys.ARG_NOTIFICATION_CALLBACK to notificationCallback) 304 | ) 305 | } 306 | } 307 | } 308 | } 309 | 310 | return true 311 | } 312 | 313 | override fun onDetachedFromActivity() { 314 | } 315 | 316 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { 317 | } 318 | 319 | override fun onAttachedToActivity(binding: ActivityPluginBinding) { 320 | activity = binding.activity 321 | binding.addOnNewIntentListener(this) 322 | } 323 | 324 | override fun onDetachedFromActivityForConfigChanges() { 325 | } 326 | 327 | 328 | } 329 | --------------------------------------------------------------------------------