├── ios
├── Assets
│ └── .gitkeep
├── Classes
│ ├── pluggables
│ │ ├── InitPluggable.h
│ │ ├── DisposePluggable.h
│ │ ├── Pluggable.h
│ │ ├── DisposePluggable.m
│ │ └── InitPluggable.m
│ ├── BackgroundLocatorPlugin.h
│ ├── Utils
│ │ ├── Util.h
│ │ └── Util.m
│ ├── Preferences
│ │ ├── PreferencesManager.h
│ │ └── PreferencesManager.m
│ ├── Helpers
│ │ ├── MethodCallHelper.h
│ │ └── MethodCallHelper.m
│ ├── Globals.h
│ ├── Globals.m
│ └── BackgroundLocatorPlugin.m
├── background_locator.podspec
└── .gitignore
├── res
└── values
│ └── strings_en.arb
├── android
├── settings.gradle
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ └── rekab
│ │ └── app
│ │ └── background_locator
│ │ ├── provider
│ │ ├── LocationRequestOptions.kt
│ │ ├── LocationUpdateListener.kt
│ │ ├── LocationClient.kt
│ │ ├── BLLocationProvider.kt
│ │ ├── GoogleLocationProviderClient.kt
│ │ ├── LocationParserUtil.kt
│ │ └── AndroidLocationProviderClient.kt
│ │ ├── pluggables
│ │ ├── Pluggable.kt
│ │ ├── DisposePluggable.kt
│ │ └── InitPluggable.kt
│ │ ├── BootBroadcastReceiver.kt
│ │ ├── IsolateHolderExtension.kt
│ │ ├── Keys.kt
│ │ ├── PreferencesManager.kt
│ │ ├── IsolateHolderService.kt
│ │ └── BackgroundLocatorPlugin.kt
├── .gitignore
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── .settings
│ └── org.eclipse.buildship.core.prefs
├── .project
└── build.gradle
├── demo.gif
├── example
├── ios
│ ├── Runner
│ │ ├── Runner-Bridging-Header.h
│ │ ├── Assets.xcassets
│ │ │ ├── LaunchImage.imageset
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ ├── README.md
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ │ └── Contents.json
│ │ ├── AppDelegate.swift
│ │ ├── Base.lproj
│ │ │ ├── Main.storyboard
│ │ │ └── LaunchScreen.storyboard
│ │ └── Info.plist
│ ├── Flutter
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── AppFrameworkInfo.plist
│ ├── Runner.xcodeproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ ├── xcshareddata
│ │ │ └── xcschemes
│ │ │ │ └── Runner.xcscheme
│ │ └── project.pbxproj
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── .gitignore
│ └── Podfile
├── android
│ ├── gradle.properties
│ ├── app
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── res
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ └── ic_location.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ └── ic_location.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ └── ic_location.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ └── ic_location.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ └── ic_location.png
│ │ │ │ │ ├── drawable
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── drawable-v21
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── values
│ │ │ │ │ │ └── styles.xml
│ │ │ │ │ └── values-night
│ │ │ │ │ │ └── styles.xml
│ │ │ │ ├── kotlin
│ │ │ │ │ └── rekab
│ │ │ │ │ │ └── app
│ │ │ │ │ │ └── background_locator_example
│ │ │ │ │ │ ├── MainActivity.kt
│ │ │ │ │ │ └── Application.kt
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ └── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── .gitignore
│ ├── settings.gradle
│ └── build.gradle
├── .metadata
├── README.md
├── lib
│ ├── file_manager.dart
│ ├── location_callback_handler.dart
│ ├── location_service_repository.dart
│ └── main.dart
├── test
│ └── widget_test.dart
├── .gitignore
└── pubspec.yaml
├── .metadata
├── test
└── background_locator_test.dart
├── lib
├── auto_stop_handler.dart
├── settings
│ ├── locator_settings.dart
│ ├── ios_settings.dart
│ └── android_settings.dart
├── location_dto.dart
├── callback_dispatcher.dart
├── utils
│ └── settings_util.dart
├── background_locator.dart
└── keys.dart
├── pubspec.yaml
├── .github
├── workflows
│ ├── build_pr.yml
│ └── build.yml
└── no-response.yml
├── LICENSE
├── .gitignore
├── README.md
└── CHANGELOG.md
/ios/Assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/res/values/strings_en.arb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'background_locator'
2 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/demo.gif
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 | android.enableR8=true
5 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_location.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_location.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_location.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_location.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_location.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekabhq/background_locator/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/provider/LocationRequestOptions.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator.provider
2 |
3 | class LocationRequestOptions(val interval: Long, val accuracy: Int, val distanceFilter: Float)
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/rekab/app/background_locator_example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator_example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity : FlutterActivity()
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/provider/LocationUpdateListener.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator.provider
2 |
3 | import java.util.HashMap
4 |
5 | interface LocationUpdateListener {
6 | fun onLocationUpdated(location: HashMap?)
7 | }
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Mar 09 11:35:09 IRST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
7 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jan 04 12:08:15 IRST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
7 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/provider/LocationClient.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator.provider
2 |
3 | enum class LocationClient(val value: Int) {
4 | Google(0), Android(1);
5 |
6 | companion object {
7 | fun fromInt(value: Int) = values().firstOrNull { it.value == value }
8 | }
9 | }
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/provider/BLLocationProvider.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator.provider
2 |
3 | interface BLLocationProvider {
4 | var listener: LocationUpdateListener?
5 |
6 | fun removeLocationUpdates()
7 |
8 | fun requestLocationUpdates(request: LocationRequestOptions)
9 | }
10 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 2d2a1ffec95cc70a3218872a2cd3f8de4933c42f
8 | channel: stable
9 |
10 | project_type: plugin
11 |
--------------------------------------------------------------------------------
/ios/Classes/pluggables/InitPluggable.h:
--------------------------------------------------------------------------------
1 | //
2 | // InitPluggable.h
3 | // background_locator
4 | //
5 | // Created by Mehdok on 6/7/21.
6 | //
7 |
8 | #import
9 | #import "Pluggable.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface InitPluggable : NSObject
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 2d2a1ffec95cc70a3218872a2cd3f8de4933c42f
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/ios/Classes/pluggables/DisposePluggable.h:
--------------------------------------------------------------------------------
1 | //
2 | // DisposePluggable.h
3 | // background_locator
4 | //
5 | // Created by Mehdok on 6/7/21.
6 | //
7 |
8 | #import
9 | #import "Pluggable.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface DisposePluggable : NSObject
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/pluggables/Pluggable.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator.pluggables
2 |
3 | import android.content.Context
4 |
5 | interface Pluggable {
6 | fun setCallback(context: Context, callbackHandle: Long)
7 | fun onServiceStart(context: Context) { /*optional*/ }
8 | fun onServiceDispose(context: Context) {/*optional*/ }
9 | }
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Classes/pluggables/Pluggable.h:
--------------------------------------------------------------------------------
1 | //
2 | // Pluggable.h
3 | // Pods
4 | //
5 | // Created by Mehdok on 6/6/21.
6 | //
7 |
8 | #ifndef Pluggable_h
9 | #define Pluggable_h
10 |
11 | @protocol Pluggable
12 | - (void) setCallback:(int64_t) callbackHandle;
13 | - (void) onServiceStart: (NSDictionary*)initialDataDictionary;
14 | - (void) onServiceDispose;
15 | @end
16 |
17 | #endif /* Pluggable_h */
18 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Classes/BackgroundLocatorPlugin.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "MethodCallHelper.h"
4 |
5 | @interface BackgroundLocatorPlugin : NSObject
6 |
7 | + (BackgroundLocatorPlugin*_Nullable) getInstance;
8 | - (void)invokeMethod:(NSString*_Nonnull)method arguments:(id _Nullable)arguments;
9 |
10 | @end
11 |
--------------------------------------------------------------------------------
/android/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | arguments=
2 | auto.sync=false
3 | build.scans.enabled=false
4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.0))
5 | connection.project.dir=
6 | eclipse.preferences.version=1
7 | gradle.user.home=
8 | java.home=/usr/lib/jvm/java-8-openjdk-amd64
9 | jvm.arguments=
10 | offline.mode=false
11 | override.workspace.settings=true
12 | show.console.view=true
13 | show.executions.view=true
14 |
--------------------------------------------------------------------------------
/test/background_locator_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/services.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 |
4 | void main() {
5 | const MethodChannel channel = MethodChannel('background_locator');
6 |
7 | setUp(() {
8 | channel.setMockMethodCallHandler((MethodCall methodCall) async {
9 | return '42';
10 | });
11 | });
12 |
13 | tearDown(() {
14 | channel.setMockMethodCallHandler(null);
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/ios/Classes/Utils/Util.h:
--------------------------------------------------------------------------------
1 | //
2 | // Util.h
3 | // background_locator
4 | //
5 | // Created by Mehdi Sohrabi on 6/28/20.
6 | //
7 |
8 | #import
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface Util : NSObject
14 |
15 | + (CLLocationAccuracy) getAccuracy:(long)key;
16 | + (NSDictionary*) getLocationMap:(CLLocation *)location;
17 |
18 | @end
19 |
20 | NS_ASSUME_NONNULL_END
21 |
--------------------------------------------------------------------------------
/android/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | android__
4 | Project android__ created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/BootBroadcastReceiver.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 |
7 | class BootBroadcastReceiver : BroadcastReceiver() {
8 | override fun onReceive(context: Context, intent: Intent) {
9 | if (intent.action == "android.intent.action.BOOT_COMPLETED") {
10 | BackgroundLocatorPlugin.registerAfterBoot(context)
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/lib/auto_stop_handler.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'background_locator.dart';
4 |
5 | class AutoStopHandler extends WidgetsBindingObserver {
6 | @override
7 | Future didChangeAppLifecycleState(AppLifecycleState state) async {
8 | switch (state) {
9 | case AppLifecycleState.inactive:
10 | case AppLifecycleState.paused:
11 | case AppLifecycleState.detached:
12 | await BackgroundLocator.unRegisterLocationUpdate();
13 | break;
14 | case AppLifecycleState.resumed:
15 | break;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # background_locator_example
2 |
3 | Demonstrates how to use the background_locator plugin.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
13 |
14 | For help getting started with Flutter, view our
15 | [online documentation](https://flutter.dev/docs), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: background_locator
2 | description: A Flutter plugin to request the location even if the app is killed. Sending the location to a dart function in background, also provide a meter filter
3 | version: 1.6.12
4 | homepage: https://github.com/rekab-app/background_locator
5 |
6 | environment:
7 | sdk: ">=2.12.0 <3.0.0"
8 | flutter: ">=1.12.0"
9 | dependencies:
10 | flutter:
11 | sdk: flutter
12 |
13 | dev_dependencies:
14 | flutter_test:
15 | sdk: flutter
16 |
17 | flutter:
18 | plugin:
19 | platforms:
20 | android:
21 | package: rekab.app.background_locator
22 | pluginClass: BackgroundLocatorPlugin
23 | ios:
24 | pluginClass: BackgroundLocatorPlugin
25 |
--------------------------------------------------------------------------------
/.github/workflows/build_pr.yml:
--------------------------------------------------------------------------------
1 | name: build_pr
2 |
3 | on:
4 | pull_request:
5 | branches: [ master ]
6 |
7 | jobs:
8 | build:
9 |
10 | runs-on: macos-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: actions/setup-java@v1
15 | with:
16 | java-version: '12.x'
17 | - uses: subosito/flutter-action@v1
18 | with:
19 | channel: 'stable'
20 |
21 | - name: Install dependencies
22 | run: flutter pub get
23 | - name: Build debug akp
24 | run: |
25 | cd example
26 | flutter build apk --debug
27 | - name: Build debug ios app
28 | run: |
29 | cd example
30 | flutter build ios --debug --no-codesign
31 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.4.31'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:4.1.3'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | jcenter()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | tags:
7 | - '*'
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: macos-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - uses: actions/setup-java@v1
17 | with:
18 | java-version: '12.x'
19 | - uses: subosito/flutter-action@v1
20 | with:
21 | channel: 'stable'
22 |
23 | - name: Install dependencies
24 | run: flutter pub get
25 | - name: Build debug akp
26 | run: |
27 | cd example
28 | flutter build apk --debug
29 | - name: Build debug ios app
30 | run: |
31 | cd example
32 | flutter build ios --debug --no-codesign
33 |
--------------------------------------------------------------------------------
/.github/no-response.yml:
--------------------------------------------------------------------------------
1 | # Configuration for probot-no-response - https://github.com/probot/no-response
2 |
3 | # Number of days of inactivity before an Issue is closed for lack of response
4 | daysUntilClose: 14
5 | # Label requiring a response
6 | responseRequiredLabel: more-information-needed
7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable
8 | closeComment: >
9 | This issue has been automatically closed because there has been no response
10 | to our request for more information from the original author. With only the
11 | information that is currently in the issue, we don't have enough information
12 | to take action. Please reach out if you have or find the answers we need so
13 | that we can investigate further.
14 |
--------------------------------------------------------------------------------
/ios/Classes/Preferences/PreferencesManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // PreferencesManager.h
3 | // background_locator
4 | //
5 | // Created by Mehdi Sohrabi on 6/28/20.
6 | //
7 |
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | @interface PreferencesManager : NSObject
13 |
14 | + (int64_t)getCallbackDispatcherHandle;
15 | + (void)setCallbackDispatcherHandle:(int64_t)handle;
16 | + (int64_t)getCallbackHandle:(NSString *)key;
17 | + (void)setCallbackHandle:(int64_t)handle key:(NSString *)key;
18 | + (void)saveDistanceFilter:(double) distance;
19 | + (double)getDistanceFilter;
20 | + (void)setObservingRegion:(BOOL) observing;
21 | + (BOOL)isObservingRegion;
22 | + (void)setServiceRunning:(BOOL) running;
23 | + (BOOL)isServiceRunning;
24 |
25 | @end
26 |
27 | NS_ASSUME_NONNULL_END
28 |
--------------------------------------------------------------------------------
/ios/background_locator.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
3 | #
4 | Pod::Spec.new do |s|
5 | s.name = 'background_locator'
6 | s.version = '0.0.1'
7 | s.summary = 'A Flutter plugin for getting location updates even when the app is killed.'
8 | s.description = <<-DESC
9 | A new Flutter plugin.
10 | DESC
11 | s.homepage = 'https://github.com/rekab-app/background_locator'
12 | s.license = { :file => '../LICENSE' }
13 | s.author = { 'REKAB' => 'mehdok@gmail.com' }
14 | s.source = { :path => '.' }
15 | s.source_files = 'Classes/**/*'
16 | s.public_header_files = 'Classes/**/*.h'
17 | s.dependency 'Flutter'
18 |
19 | s.ios.deployment_target = '8.0'
20 | end
21 |
22 |
--------------------------------------------------------------------------------
/lib/settings/locator_settings.dart:
--------------------------------------------------------------------------------
1 | class LocationAccuracy {
2 | const LocationAccuracy._internal(this.value);
3 |
4 | final int value;
5 |
6 | static const POWERSAVE = LocationAccuracy._internal(0);
7 | static const LOW = LocationAccuracy._internal(1);
8 | static const BALANCED = LocationAccuracy._internal(2);
9 | static const HIGH = LocationAccuracy._internal(3);
10 | static const NAVIGATION = LocationAccuracy._internal(4);
11 | }
12 |
13 | class LocatorSettings {
14 | final LocationAccuracy accuracy;
15 | final double distanceFilter;
16 |
17 | /// [accuracy] The accuracy of location, Default is max accuracy NAVIGATION.
18 | ///
19 | /// [distanceFilter] distance in meter to trigger location update, Default is 0 meter.
20 | const LocatorSettings({required this.accuracy, required this.distanceFilter});
21 | }
22 |
--------------------------------------------------------------------------------
/ios/Classes/pluggables/DisposePluggable.m:
--------------------------------------------------------------------------------
1 | //
2 | // DisposePluggable.m
3 | // background_locator
4 | //
5 | // Created by Mehdok on 6/7/21.
6 | //
7 |
8 | #import "DisposePluggable.h"
9 | #import "PreferencesManager.h"
10 | #import "Globals.h"
11 | #import "BackgroundLocatorPlugin.h"
12 |
13 | @implementation DisposePluggable
14 |
15 | - (void)onServiceDispose {
16 | NSDictionary *map = @{
17 | kArgDisposeCallback : @([PreferencesManager getCallbackHandle:kDisposeCallbackKey])
18 | };
19 | [[BackgroundLocatorPlugin getInstance] invokeMethod:kBCMDispose arguments:map];
20 | }
21 |
22 | - (void)onServiceStart:(NSDictionary *)initialDataDictionary {
23 | // nop
24 | }
25 |
26 | - (void)setCallback:(int64_t)callbackHandle {
27 | [PreferencesManager setCallbackHandle:callbackHandle key:kDisposeCallbackKey];
28 | }
29 |
30 | @end
31 |
--------------------------------------------------------------------------------
/example/lib/file_manager.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:path_provider/path_provider.dart';
4 |
5 | class FileManager {
6 | static Future writeToLogFile(String log) async {
7 | final file = await _getTempLogFile();
8 | await file.writeAsString(log, mode: FileMode.append);
9 | }
10 |
11 | static Future readLogFile() async {
12 | final file = await _getTempLogFile();
13 | return file.readAsString();
14 | }
15 |
16 | static Future _getTempLogFile() async {
17 | final directory = await getTemporaryDirectory();
18 | final file = File('${directory.path}/log.txt');
19 | if (!await file.exists()) {
20 | await file.writeAsString('');
21 | }
22 | return file;
23 | }
24 |
25 | static Future clearLogFile() async {
26 | final file = await _getTempLogFile();
27 | await file.writeAsString('');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 9.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:background_locator_example/main.dart';
9 | import 'package:flutter/material.dart';
10 | import 'package:flutter_test/flutter_test.dart';
11 |
12 | void main() {
13 | testWidgets('Verify Platform version', (WidgetTester tester) async {
14 | // Build our app and trigger a frame.
15 | await tester.pumpWidget(MyApp());
16 |
17 | // Verify that platform version is retrieved.
18 | expect(
19 | find.byWidgetPredicate(
20 | (Widget widget) =>
21 | widget is Text && widget.data.startsWith('Running on:'),
22 | ),
23 | findsOneWidget,
24 | );
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/rekab/app/background_locator_example/Application.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator_example
2 |
3 | import io.flutter.app.FlutterApplication
4 | import io.flutter.plugin.common.PluginRegistry
5 | import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
6 | import io.flutter.plugins.pathprovider.PathProviderPlugin
7 | import io.flutter.view.FlutterMain
8 | import rekab.app.background_locator.IsolateHolderService
9 |
10 | //class Application : FlutterApplication(), PluginRegistrantCallback {
11 | // override fun onCreate() {
12 | // super.onCreate()
13 | // IsolateHolderService.setPluginRegistrant(this)
14 | // FlutterMain.startInitialization(this)
15 | // }
16 | //
17 | // override fun registerWith(registry: PluginRegistry?) {
18 | // if (!registry!!.hasPlugin("io.flutter.plugins.pathprovider")) {
19 | // PathProviderPlugin.registerWith(registry.registrarFor("io.flutter.plugins.pathprovider"))
20 | // }
21 | // }
22 | //}
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2020 REKAB
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/example/lib/location_callback_handler.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:background_locator/location_dto.dart';
4 |
5 | import 'location_service_repository.dart';
6 |
7 | class LocationCallbackHandler {
8 | static Future initCallback(Map params) async {
9 | LocationServiceRepository myLocationCallbackRepository =
10 | LocationServiceRepository();
11 | await myLocationCallbackRepository.init(params);
12 | }
13 |
14 | static Future disposeCallback() async {
15 | LocationServiceRepository myLocationCallbackRepository =
16 | LocationServiceRepository();
17 | await myLocationCallbackRepository.dispose();
18 | }
19 |
20 | static Future callback(LocationDto locationDto) async {
21 | LocationServiceRepository myLocationCallbackRepository =
22 | LocationServiceRepository();
23 | await myLocationCallbackRepository.callback(locationDto);
24 | }
25 |
26 | static Future notificationCallback() async {
27 | print('***notificationCallback');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ios/Classes/Helpers/MethodCallHelper.h:
--------------------------------------------------------------------------------
1 | //
2 | // MethodCallHelper.h
3 | // background_locator
4 | //
5 | // Created by Mehdi Sohrabi on 6/28/20.
6 | //
7 |
8 | #import
9 | #import
10 |
11 | @protocol MethodCallHelperDelegate
12 | - (void) startLocatorService:(int64_t) callbackDispatcher;
13 | - (void)registerLocator:(int64_t)callback
14 | initCallback:(int64_t)initCallback
15 | initialDataDictionary:(NSDictionary *_Nullable)initialDataDictionary
16 | disposeCallback:(int64_t)disposeCallback
17 | settings:(NSDictionary *_Nonnull)settings;
18 | - (void) removeLocator;
19 | - (BOOL) isServiceRunning;
20 | - (void) setServiceRunning:(BOOL)value;
21 |
22 | @end
23 |
24 | NS_ASSUME_NONNULL_BEGIN
25 |
26 | @interface MethodCallHelper : NSObject
27 |
28 | - (void)handleMethodCall:(FlutterMethodCall *)call
29 | result:(FlutterResult)result
30 | delegate:(id )delegate;
31 |
32 | @end
33 |
34 | NS_ASSUME_NONNULL_END
35 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/settings/ios_settings.dart:
--------------------------------------------------------------------------------
1 | import 'package:background_locator/keys.dart';
2 |
3 | import 'locator_settings.dart';
4 |
5 | class IOSSettings extends LocatorSettings {
6 | /// [accuracy] The accuracy of location, Default is max accuracy NAVIGATION.
7 | ///
8 | /// [distanceFilter] distance in meter to trigger location update, Default is 0 meter.
9 | ///
10 | /// [showsBackgroundLocationIndicator] The background location usage indicator is a blue bar or a blue pill in the status bar on iOS. Default is false.
11 |
12 | final bool showsBackgroundLocationIndicator;
13 | const IOSSettings({
14 | LocationAccuracy accuracy = LocationAccuracy.NAVIGATION,
15 | double distanceFilter = 0,
16 | this.showsBackgroundLocationIndicator = false,
17 | }) : super(accuracy: accuracy, distanceFilter: distanceFilter); //minutes
18 |
19 | Map toMap() {
20 | return {
21 | Keys.SETTINGS_ACCURACY: accuracy.value,
22 | Keys.SETTINGS_DISTANCE_FILTER: distanceFilter,
23 | Keys.SETTINGS_IOS_SHOWS_BACKGROUND_LOCATION_INDICATOR:
24 | showsBackgroundLocationIndicator,
25 | };
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import background_locator
2 | import Flutter
3 | import path_provider_ios
4 | import UIKit
5 |
6 | func registerPlugins(registry: FlutterPluginRegistry) {
7 | GeneratedPluginRegistrant.register(with: registry)
8 | }
9 |
10 | @UIApplicationMain
11 | @objc class AppDelegate: FlutterAppDelegate {
12 | override func application(
13 | _ application: UIApplication,
14 | didFinishLaunchingWithOptions launchOptions: [UIApplication
15 | .LaunchOptionsKey: Any]?
16 | ) -> Bool {
17 | GeneratedPluginRegistrant.register(with: self)
18 | BackgroundLocatorPlugin.setPluginRegistrantCallback(registerPlugins)
19 |
20 | registerOtherPlugins()
21 |
22 | return super
23 | .application(application,
24 | didFinishLaunchingWithOptions: launchOptions)
25 | }
26 |
27 | func registerOtherPlugins() {
28 | if !hasPlugin("io.flutter.plugins.pathprovider") {
29 | FLTPathProviderPlugin
30 | .register(with: registrar(forPlugin: "io.flutter.plugins.pathprovider")!)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | group 'rekab.app.background_locator'
2 | version '1.0-SNAPSHOT'
3 |
4 | buildscript {
5 | ext.kotlin_version = '1.4.21'
6 | repositories {
7 | google()
8 | jcenter()
9 | }
10 |
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:4.1.1'
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 | }
15 | }
16 |
17 | rootProject.allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 | }
22 | }
23 |
24 | apply plugin: 'com.android.library'
25 | apply plugin: 'kotlin-android'
26 |
27 | android {
28 | compileSdkVersion 30
29 |
30 | sourceSets {
31 | main.java.srcDirs += 'src/main/kotlin'
32 | }
33 | defaultConfig {
34 | minSdkVersion 16
35 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
36 | }
37 | lintOptions {
38 | disable 'InvalidPackage'
39 | }
40 | }
41 |
42 | dependencies {
43 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
44 | implementation "com.google.android.gms:play-services-location:18.0.0"
45 | implementation 'com.google.code.gson:gson:2.8.6'
46 | }
47 |
--------------------------------------------------------------------------------
/ios/Classes/pluggables/InitPluggable.m:
--------------------------------------------------------------------------------
1 | //
2 | // InitPluggable.m
3 | // background_locator
4 | //
5 | // Created by Mehdok on 6/7/21.
6 | //
7 |
8 | #import "InitPluggable.h"
9 | #import "PreferencesManager.h"
10 | #import "Globals.h"
11 | #import "BackgroundLocatorPlugin.h"
12 |
13 | @implementation InitPluggable {
14 | BOOL isInitCallbackCalled;
15 | }
16 |
17 | - (instancetype)init {
18 | self = [super init];
19 | if (self) {
20 | isInitCallbackCalled = NO;
21 | }
22 | return self;
23 | }
24 |
25 | - (void)onServiceDispose {
26 | isInitCallbackCalled = NO;
27 | }
28 |
29 | - (void)onServiceStart:(NSDictionary*) initialDataDictionary {
30 | if (!isInitCallbackCalled) {
31 | NSDictionary *map = @{
32 | kArgInitCallback : @([PreferencesManager getCallbackHandle:kInitCallbackKey]),
33 | kArgInitDataCallback: initialDataDictionary
34 | };
35 | [[BackgroundLocatorPlugin getInstance] invokeMethod:kBCMInit arguments:map];
36 | }
37 | isInitCallbackCalled = YES;
38 | }
39 |
40 | - (void)setCallback:(int64_t)callbackHandle {
41 | [PreferencesManager setCallbackHandle:callbackHandle key:kInitCallbackKey];
42 | }
43 |
44 | @end
45 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/pluggables/DisposePluggable.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator.pluggables
2 |
3 | import android.content.Context
4 | import android.os.Handler
5 | import io.flutter.plugin.common.MethodChannel
6 | import rekab.app.background_locator.IsolateHolderService
7 | import rekab.app.background_locator.Keys
8 | import rekab.app.background_locator.PreferencesManager
9 |
10 | class DisposePluggable : Pluggable {
11 | override fun setCallback(context: Context, callbackHandle: Long) {
12 | PreferencesManager.setCallbackHandle(context, Keys.DISPOSE_CALLBACK_HANDLE_KEY, callbackHandle)
13 | }
14 |
15 | override fun onServiceDispose(context: Context) {
16 | (PreferencesManager.getCallbackHandle(context, Keys.DISPOSE_CALLBACK_HANDLE_KEY))?.let { disposeCallback ->
17 | val backgroundChannel = MethodChannel(IsolateHolderService.backgroundEngine?.dartExecutor?.binaryMessenger,
18 | Keys.BACKGROUND_CHANNEL_ID)
19 | Handler(context.mainLooper)
20 | .post {
21 | backgroundChannel.invokeMethod(Keys.BCM_DISPOSE,
22 | hashMapOf(Keys.ARG_DISPOSE_CALLBACK to disposeCallback))
23 | }
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/ios/Classes/Utils/Util.m:
--------------------------------------------------------------------------------
1 | //
2 | // Util.m
3 | // background_locator
4 | //
5 | // Created by Mehdi Sohrabi on 6/28/20.
6 | //
7 |
8 | #import "Util.h"
9 | #import "GLobals.h"
10 |
11 | @implementation Util
12 |
13 | + (CLLocationAccuracy) getAccuracy:(long)key {
14 | switch (key) {
15 | case 0:
16 | return kCLLocationAccuracyKilometer;
17 | case 1:
18 | return kCLLocationAccuracyHundredMeters;
19 | case 2:
20 | return kCLLocationAccuracyNearestTenMeters;
21 | case 3:
22 | return kCLLocationAccuracyBest;
23 | case 4:
24 | return kCLLocationAccuracyBestForNavigation;
25 | default:
26 | return kCLLocationAccuracyBestForNavigation;
27 | }
28 | }
29 |
30 | + (NSDictionary *)getLocationMap:(CLLocation *)location {
31 | NSTimeInterval timeInSeconds = [location.timestamp timeIntervalSince1970];
32 | return @{
33 | kArgLatitude: @(location.coordinate.latitude),
34 | kArgLongitude: @(location.coordinate.longitude),
35 | kArgAccuracy: @(location.horizontalAccuracy),
36 | kArgAltitude: @(location.altitude),
37 | kArgSpeed: @(location.speed),
38 | kArgSpeedAccuracy: @(0.0),
39 | kArgHeading: @(location.course),
40 | kArgTime: @(((double) timeInSeconds) * 1000.0) // in milliseconds since the epoch
41 | };
42 | }
43 |
44 | @end
45 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/provider/GoogleLocationProviderClient.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator.provider
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import com.google.android.gms.location.*
6 |
7 | class GoogleLocationProviderClient(context: Context, override var listener: LocationUpdateListener?) : BLLocationProvider {
8 | private val client: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
9 | private val locationCallback = LocationListener(listener)
10 |
11 | override fun removeLocationUpdates() {
12 | client.removeLocationUpdates(locationCallback)
13 | }
14 |
15 | @SuppressLint("MissingPermission")
16 | override fun requestLocationUpdates(request: LocationRequestOptions) {
17 | client.requestLocationUpdates(getLocationRequest(request), locationCallback, null)
18 | }
19 |
20 | private fun getLocationRequest(request: LocationRequestOptions): LocationRequest {
21 | val locationRequest = LocationRequest.create()
22 |
23 | locationRequest.interval = request.interval
24 | locationRequest.fastestInterval = request.interval
25 | locationRequest.maxWaitTime = request.interval
26 | locationRequest.priority = request.accuracy
27 | locationRequest.smallestDisplacement = request.distanceFilter
28 |
29 | return locationRequest
30 | }
31 | }
32 |
33 | private class LocationListener(val listener: LocationUpdateListener?) : LocationCallback() {
34 | override fun onLocationResult(location: LocationResult?) {
35 | listener?.onLocationUpdated(LocationParserUtil.getLocationMapFromLocation(location))
36 | }
37 | }
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.lock
4 | *.log
5 | *.pyc
6 | *.swp
7 | .DS_Store
8 | .atom/
9 | .buildlog/
10 | .history
11 | .svn/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # Visual Studio Code related
20 | .vscode/
21 |
22 | # Flutter/Dart/Pub related
23 | **/doc/api/
24 | .dart_tool/
25 | .flutter-plugins
26 | .packages
27 | .pub-cache/
28 | .pub/
29 | build/
30 | lib/main_widget_test.dart
31 | coverage
32 | # Android related
33 | **/android/**/gradle-wrapper.jar
34 | **/android/.gradle
35 | **/android/captures/
36 | **/android/gradlew
37 | **/android/gradlew.bat
38 | **/android/local.properties
39 | **/android/**/GeneratedPluginRegistrant.java
40 |
41 | # iOS/XCode related
42 | **/ios/**/*.mode1v3
43 | **/ios/**/*.mode2v3
44 | **/ios/**/*.moved-aside
45 | **/ios/**/*.pbxuser
46 | **/ios/**/*.perspectivev3
47 | **/ios/**/*sync/
48 | **/ios/**/.sconsign.dblite
49 | **/ios/**/.tags*
50 | **/ios/**/.vagrant/
51 | **/ios/**/DerivedData/
52 | **/ios/**/Icon?
53 | **/ios/**/Pods/
54 | **/ios/**/.symlinks/
55 | **/ios/**/profile
56 | **/ios/**/xcuserdata
57 | **/ios/.generated/
58 | **/ios/Flutter/App.framework
59 | **/ios/Flutter/Flutter.framework
60 | **/ios/Flutter/Generated.xcconfig
61 | **/ios/Flutter/app.flx
62 | **/ios/Flutter/app.zip
63 | **/ios/Flutter/flutter_assets/
64 | **/ios/ServiceDefinitions.json
65 | **/ios/Runner/GeneratedPluginRegistrant.*
66 | **/ios/.symlinks
67 |
68 | # Exceptions to above rules.
69 | !**/ios/**/default.mode1v3
70 | !**/ios/**/default.mode2v3
71 | !**/ios/**/default.pbxuser
72 | !**/ios/**/default.perspectivev3
73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
74 | ios/Frameworks/
75 | .flutter-plugins-dependencies
76 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.lock
4 | *.log
5 | *.pyc
6 | *.swp
7 | .DS_Store
8 | .atom/
9 | .buildlog/
10 | .history
11 | .svn/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | .vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | build/
33 |
34 | # Android related
35 | **/android/**/gradle-wrapper.jar
36 | **/android/.gradle
37 | **/android/captures/
38 | **/android/gradlew
39 | **/android/gradlew.bat
40 | **/android/local.properties
41 | **/android/**/GeneratedPluginRegistrant.java
42 |
43 | # iOS/XCode related
44 | **/ios/**/*.mode1v3
45 | **/ios/**/*.mode2v3
46 | **/ios/**/*.moved-aside
47 | **/ios/**/*.pbxuser
48 | **/ios/**/*.perspectivev3
49 | **/ios/**/*sync/
50 | **/ios/**/.sconsign.dblite
51 | **/ios/**/.tags*
52 | **/ios/**/.vagrant/
53 | **/ios/**/DerivedData/
54 | **/ios/**/Icon?
55 | **/ios/**/Pods/
56 | **/ios/**/.symlinks/
57 | **/ios/**/profile
58 | **/ios/**/xcuserdata
59 | **/ios/.generated/
60 | **/ios/Flutter/App.framework
61 | **/ios/Flutter/Flutter.framework
62 | **/ios/Flutter/Flutter.podspec
63 | **/ios/Flutter/Generated.xcconfig
64 | **/ios/Flutter/ephemeral
65 | **/ios/Flutter/app.flx
66 | **/ios/Flutter/app.zip
67 | **/ios/Flutter/flutter_assets/
68 | **/ios/Flutter/flutter_export_environment.sh
69 | **/ios/ServiceDefinitions.json
70 | **/ios/Runner/GeneratedPluginRegistrant.*
71 |
72 | # Exceptions to above rules.
73 | !**/ios/**/default.mode1v3
74 | !**/ios/**/default.mode2v3
75 | !**/ios/**/default.pbxuser
76 | !**/ios/**/default.perspectivev3
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # background_locator  [](https://pub.dartlang.org/packages/background_locator)  
3 |
4 | A Flutter plugin for getting location updates even when the app is killed.
5 |
6 | 
7 |
8 | Refer to [wiki](https://github.com/rekab-app/background_locator/wiki) page for install and setup instruction or jump to specific subject with below links:
9 |
10 | * [Installation](https://github.com/rekab-app/background_locator/wiki/Installation)
11 | * [Setup](https://github.com/rekab-app/background_locator/wiki/Setup)
12 | * [How to use](https://github.com/rekab-app/background_locator/wiki/How-to-use)
13 | * [Use other plugins in callback](https://github.com/rekab-app/background_locator/wiki/Use-other-plugins-in-callback)
14 | * [Stop on app terminate](https://github.com/rekab-app/background_locator/wiki/Stop-on-app-terminate)
15 | * [LocationSettings options](https://github.com/rekab-app/background_locator/wiki/LocationSettings-options)
16 | * [Restart service on device reboot (Android only)](https://github.com/rekab-app/background_locator/wiki/Restart-service-on-device-reboot)
17 |
18 | ## License
19 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
20 |
21 | ## Contributor
22 | Thanks to all who contributed on this plugin to fix bugs and adding new feature, including:
23 | * [Gerardo Ibarra](https://github.com/gpibarra)
24 | * [RomanJos](https://github.com/RomanJos)
25 | * [Marcelo Henrique Neppel](https://github.com/marceloneppel)
26 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/pluggables/InitPluggable.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator.pluggables
2 |
3 | import android.content.Context
4 | import android.os.Handler
5 | import io.flutter.plugin.common.MethodChannel
6 | import rekab.app.background_locator.IsolateHolderService
7 | import rekab.app.background_locator.Keys
8 | import rekab.app.background_locator.PreferencesManager
9 |
10 | class InitPluggable : Pluggable {
11 | private var isInitCallbackCalled = false
12 |
13 | override fun setCallback(context: Context, callbackHandle: Long) {
14 | PreferencesManager.setCallbackHandle(context, Keys.INIT_CALLBACK_HANDLE_KEY, callbackHandle)
15 |
16 | }
17 |
18 | override fun onServiceStart(context: Context) {
19 | if (!isInitCallbackCalled) {
20 | (PreferencesManager.getCallbackHandle(context, Keys.INIT_CALLBACK_HANDLE_KEY))?.let { initCallback ->
21 | val initialDataMap = PreferencesManager.getDataCallback(context, Keys.INIT_DATA_CALLBACK_KEY)
22 | val backgroundChannel = MethodChannel(IsolateHolderService.backgroundEngine?.dartExecutor?.binaryMessenger,
23 | Keys.BACKGROUND_CHANNEL_ID)
24 | Handler(context.mainLooper)
25 | .post {
26 | backgroundChannel.invokeMethod(Keys.BCM_INIT,
27 | hashMapOf(Keys.ARG_INIT_CALLBACK to initCallback, Keys.ARG_INIT_DATA_CALLBACK to initialDataMap))
28 | }
29 | }
30 | isInitCallbackCalled = true
31 | }
32 | }
33 |
34 | override fun onServiceDispose(context: Context) {
35 | isInitCallbackCalled = false
36 | }
37 |
38 | fun setInitData(context: Context, data: Map<*, *>) {
39 | PreferencesManager.setDataCallback(context, Keys.INIT_DATA_CALLBACK_KEY, data)
40 | }
41 | }
--------------------------------------------------------------------------------
/lib/location_dto.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io' show Platform;
2 |
3 | import 'keys.dart';
4 |
5 | class LocationDto {
6 | final double latitude;
7 | final double longitude;
8 | final double accuracy;
9 | final double altitude;
10 | final double speed;
11 | final double speedAccuracy;
12 | final double heading;
13 | final double time;
14 | final bool isMocked;
15 | final String provider;
16 |
17 | LocationDto._(
18 | this.latitude,
19 | this.longitude,
20 | this.accuracy,
21 | this.altitude,
22 | this.speed,
23 | this.speedAccuracy,
24 | this.heading,
25 | this.time,
26 | this.isMocked,
27 | this.provider,
28 | );
29 |
30 | factory LocationDto.fromJson(Map json) {
31 | bool isLocationMocked =
32 | Platform.isAndroid ? json[Keys.ARG_IS_MOCKED] : false;
33 | return LocationDto._(
34 | json[Keys.ARG_LATITUDE],
35 | json[Keys.ARG_LONGITUDE],
36 | json[Keys.ARG_ACCURACY],
37 | json[Keys.ARG_ALTITUDE],
38 | json[Keys.ARG_SPEED],
39 | json[Keys.ARG_SPEED_ACCURACY],
40 | json[Keys.ARG_HEADING],
41 | json[Keys.ARG_TIME],
42 | isLocationMocked,
43 | json[Keys.ARG_PROVIDER],
44 | );
45 | }
46 |
47 | Map toJson() {
48 | return {
49 | Keys.ARG_LATITUDE: this.latitude,
50 | Keys.ARG_LONGITUDE: this.longitude,
51 | Keys.ARG_ACCURACY: this.accuracy,
52 | Keys.ARG_ALTITUDE: this.altitude,
53 | Keys.ARG_SPEED: this.speed,
54 | Keys.ARG_SPEED_ACCURACY: this.speedAccuracy,
55 | Keys.ARG_HEADING: this.heading,
56 | Keys.ARG_TIME: this.time,
57 | Keys.ARG_IS_MOCKED: this.isMocked,
58 | Keys.ARG_PROVIDER: this.provider,
59 | };
60 | }
61 |
62 | @override
63 | String toString() {
64 | return 'LocationDto{latitude: $latitude, longitude: $longitude, accuracy: $accuracy, altitude: $altitude, speed: $speed, speedAccuracy: $speedAccuracy, heading: $heading, time: $time, isMocked: $isMocked, provider: $provider}';
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/ios/Classes/Preferences/PreferencesManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // PreferencesManager.m
3 | // background_locator
4 | //
5 | // Created by Mehdi Sohrabi on 6/28/20.
6 | //
7 |
8 | #import "PreferencesManager.h"
9 | #import "Globals.h"
10 |
11 | @implementation PreferencesManager
12 |
13 | + (int64_t)getCallbackDispatcherHandle {
14 | id handle = [[NSUserDefaults standardUserDefaults]
15 | objectForKey: kCallbackDispatcherKey];
16 | if (handle == nil) {
17 | return 0;
18 | }
19 | return [handle longLongValue];
20 | }
21 |
22 | + (void)setCallbackDispatcherHandle:(int64_t)handle {
23 | [[NSUserDefaults standardUserDefaults]
24 | setObject:[NSNumber numberWithLongLong:handle]
25 | forKey:kCallbackDispatcherKey];
26 | }
27 |
28 | + (int64_t)getCallbackHandle:(NSString *)key {
29 | id handle = [[NSUserDefaults standardUserDefaults]
30 | objectForKey: key];
31 | if (handle == nil) {
32 | return 0;
33 | }
34 | return [handle longLongValue];
35 | }
36 |
37 | + (void)setCallbackHandle:(int64_t)handle key:(NSString *)key {
38 | [[NSUserDefaults standardUserDefaults]
39 | setObject:[NSNumber numberWithLongLong:handle]
40 | forKey: key];
41 | }
42 |
43 | + (void)saveDistanceFilter:(double)distance {
44 | [[NSUserDefaults standardUserDefaults] setDouble:distance forKey:kDistanceFilterKey];
45 | }
46 |
47 | + (double)getDistanceFilter {
48 | return [[NSUserDefaults standardUserDefaults] doubleForKey:kDistanceFilterKey];
49 | }
50 |
51 | + (void)setObservingRegion:(BOOL)observing {
52 | [[NSUserDefaults standardUserDefaults] setBool:observing forKey:kPrefObservingRegion];
53 | }
54 |
55 | + (BOOL)isObservingRegion {
56 | return [[NSUserDefaults standardUserDefaults] boolForKey:kPrefObservingRegion];
57 | }
58 |
59 | + (void)setServiceRunning:(BOOL)running {
60 | [[NSUserDefaults standardUserDefaults] setBool:running forKey:kPrefServiceRunning];
61 | }
62 |
63 | + (BOOL)isServiceRunning {
64 | return [[NSUserDefaults standardUserDefaults] boolForKey:kPrefServiceRunning];
65 | }
66 |
67 | @end
68 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | example
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | NSLocationAlwaysAndWhenInUseUsageDescription
26 | Location service needed.
27 | NSLocationAlwaysUsageDescription
28 | Location service needed.
29 | NSLocationWhenInUseUsageDescription
30 | Location service needed.
31 | UIBackgroundModes
32 |
33 | location
34 |
35 | UILaunchStoryboardName
36 | LaunchScreen
37 | UIMainStoryboardFile
38 | Main
39 | UISupportedInterfaceOrientations
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationLandscapeLeft
43 | UIInterfaceOrientationLandscapeRight
44 |
45 | UISupportedInterfaceOrientations~ipad
46 |
47 | UIInterfaceOrientationPortrait
48 | UIInterfaceOrientationPortraitUpsideDown
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 | UIViewControllerBasedStatusBarAppearance
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/lib/callback_dispatcher.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:flutter/services.dart';
4 | import 'package:flutter/widgets.dart';
5 |
6 | import 'keys.dart';
7 | import 'location_dto.dart';
8 |
9 | @pragma('vm:entry-point')
10 | void callbackDispatcher() {
11 | const MethodChannel _backgroundChannel =
12 | MethodChannel(Keys.BACKGROUND_CHANNEL_ID);
13 | WidgetsFlutterBinding.ensureInitialized();
14 |
15 | _backgroundChannel.setMethodCallHandler((MethodCall call) async {
16 | if (Keys.BCM_SEND_LOCATION == call.method) {
17 | final Map args = call.arguments;
18 | final Function callback = PluginUtilities.getCallbackFromHandle(
19 | CallbackHandle.fromRawHandle(args[Keys.ARG_CALLBACK]))!;
20 | final LocationDto location =
21 | LocationDto.fromJson(args[Keys.ARG_LOCATION]);
22 | callback(location);
23 | } else if (Keys.BCM_NOTIFICATION_CLICK == call.method) {
24 | final Map args = call.arguments;
25 | final Function? notificationCallback =
26 | PluginUtilities.getCallbackFromHandle(CallbackHandle.fromRawHandle(
27 | args[Keys.ARG_NOTIFICATION_CALLBACK]));
28 | if (notificationCallback != null) {
29 | notificationCallback();
30 | }
31 | } else if (Keys.BCM_INIT == call.method) {
32 | final Map args = call.arguments;
33 | final Function? initCallback = PluginUtilities.getCallbackFromHandle(
34 | CallbackHandle.fromRawHandle(args[Keys.ARG_INIT_CALLBACK]));
35 | Map? data = args[Keys.ARG_INIT_DATA_CALLBACK];
36 | if (initCallback != null) {
37 | initCallback(data);
38 | }
39 | } else if (Keys.BCM_DISPOSE == call.method) {
40 | final Map args = call.arguments;
41 | final Function? disposeCallback = PluginUtilities.getCallbackFromHandle(
42 | CallbackHandle.fromRawHandle(args[Keys.ARG_DISPOSE_CALLBACK]));
43 | if (disposeCallback != null) {
44 | disposeCallback();
45 | }
46 | }
47 | });
48 | _backgroundChannel.invokeMethod(Keys.METHOD_SERVICE_INITIALIZED);
49 | }
50 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: background_locator_example
2 | description: Demonstrates how to use the background_locator plugin.
3 | publish_to: 'none'
4 |
5 | environment:
6 | sdk: ">=2.8.0 <3.0.0"
7 |
8 | dependencies:
9 | flutter:
10 | sdk: flutter
11 |
12 | # The following adds the Cupertino Icons font to your application.
13 | # Use with the CupertinoIcons class for iOS style icons.
14 | cupertino_icons: ^1.0.2
15 |
16 | dev_dependencies:
17 | flutter_test:
18 | sdk: flutter
19 |
20 | background_locator:
21 | path: ../
22 |
23 | path_provider: ^2.0.8
24 | location_permissions: ^3.0.0+1
25 |
26 | # For information on the generic Dart part of this file, see the
27 | # following page: https://dart.dev/tools/pub/pubspec
28 |
29 | # The following section is specific to Flutter.
30 | flutter:
31 |
32 | # The following line ensures that the Material Icons font is
33 | # included with your application, so that you can use the icons in
34 | # the material Icons class.
35 | uses-material-design: true
36 |
37 | # To add assets to your application, add an assets section, like this:
38 | # assets:
39 | # - images/a_dot_burr.jpeg
40 | # - images/a_dot_ham.jpeg
41 |
42 | # An image asset can refer to one or more resolution-specific "variants", see
43 | # https://flutter.dev/assets-and-images/#resolution-aware.
44 |
45 | # For details regarding adding assets from package dependencies, see
46 | # https://flutter.dev/assets-and-images/#from-packages
47 |
48 | # To add custom fonts to your application, add a fonts section here,
49 | # in this "flutter" section. Each entry in this list should have a
50 | # "family" key with the font family name, and a "fonts" key with a
51 | # list giving the asset and other descriptors for the font. For
52 | # example:
53 | # fonts:
54 | # - family: Schyler
55 | # fonts:
56 | # - asset: fonts/Schyler-Regular.ttf
57 | # - asset: fonts/Schyler-Italic.ttf
58 | # style: italic
59 | # - family: Trajan Pro
60 | # fonts:
61 | # - asset: fonts/TrajanPro.ttf
62 | # - asset: fonts/TrajanPro_Bold.ttf
63 | # weight: 700
64 | #
65 | # For details regarding fonts from package dependencies,
66 | # see https://flutter.dev/custom-fonts/#from-packages
67 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion 30
30 |
31 | sourceSets {
32 | main.java.srcDirs += 'src/main/kotlin'
33 | }
34 |
35 | lintOptions {
36 | disable 'InvalidPackage'
37 | }
38 |
39 | defaultConfig {
40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
41 | applicationId "rekab.app.background_locator_example"
42 | minSdkVersion 16
43 | targetSdkVersion 30
44 | versionCode flutterVersionCode.toInteger()
45 | versionName flutterVersionName
46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
47 | }
48 |
49 | buildTypes {
50 | release {
51 | // TODO: Add your own signing config for the release build.
52 | // Signing with the debug keys for now, so `flutter run --release` works.
53 | signingConfig signingConfigs.debug
54 | }
55 | }
56 | }
57 |
58 | flutter {
59 | source '../..'
60 | }
61 |
62 | dependencies {
63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
64 | testImplementation 'junit:junit:4.12'
65 | androidTestImplementation 'androidx.test:runner:1.3.0'
66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
67 | }
68 |
--------------------------------------------------------------------------------
/ios/Classes/Helpers/MethodCallHelper.m:
--------------------------------------------------------------------------------
1 | //
2 | // MethodCallHelper.m
3 | // background_locator
4 | //
5 | // Created by Mehdi Sohrabi on 6/28/20.
6 | //
7 |
8 | #import "MethodCallHelper.h"
9 | #import "Globals.h"
10 |
11 | @implementation MethodCallHelper
12 |
13 | - (void)handleMethodCall:(FlutterMethodCall *)call
14 | result:(FlutterResult)result
15 | delegate:(id )delegate {
16 | NSDictionary *arguments = call.arguments;
17 | if ([kMethodPluginInitializeService isEqualToString:call.method]) {
18 | int64_t callbackDispatcher = [[arguments objectForKey:kArgCallbackDispatcher] longLongValue];
19 | [delegate startLocatorService:callbackDispatcher];
20 | result(@(YES));
21 | } else if ([kMethodServiceInitialized isEqualToString:call.method]) {
22 | result(nil);
23 | } else if ([kMethodPluginRegisterLocationUpdate isEqualToString:call.method]) {
24 | int64_t callbackHandle = [[arguments objectForKey:kArgCallback] longLongValue];
25 | int64_t initCallbackHandle = [[arguments objectForKey:kArgInitCallback] longLongValue];
26 | NSDictionary *initialDataDictionary = [arguments objectForKey:kArgInitDataCallback];
27 | int64_t disposeCallbackHandle = [[arguments objectForKey:kArgDisposeCallback] longLongValue];
28 | [delegate setServiceRunning:true];
29 | [delegate registerLocator:callbackHandle initCallback:initCallbackHandle initialDataDictionary:initialDataDictionary disposeCallback:disposeCallbackHandle settings:arguments];
30 | result(@(YES));
31 | } else if ([kMethodPluginUnRegisterLocationUpdate isEqualToString:call.method]) {
32 | [delegate removeLocator];
33 | [delegate setServiceRunning:false];
34 | result(@(YES));
35 | } else if ([kMethodPluginIsRegisteredLocationUpdate isEqualToString:call.method]) {
36 | BOOL val = [delegate isServiceRunning];
37 | result(@(val));
38 | }else if ([kMethodPluginIsServiceRunning isEqualToString:call.method]) {
39 | BOOL val = [delegate isServiceRunning];
40 | result(@(val));
41 | } else if([kMethodPluginUpdateNotification isEqualToString:call.method]) {
42 | // updating notification's text is just for android
43 | result(nil);
44 | } else {
45 | result(FlutterMethodNotImplemented);
46 | }
47 | }
48 |
49 | @end
50 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Classes/Globals.h:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.h
3 | // background_locator
4 | //
5 | // Created by Mehdi Sohrabi on 6/3/20.
6 | //
7 |
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | @interface Globals : NSObject
13 |
14 | FOUNDATION_EXPORT NSString *const kCallbackDispatcherKey;
15 | FOUNDATION_EXPORT NSString *const kCallbackKey;
16 | FOUNDATION_EXPORT NSString *const kInitCallbackKey;
17 | FOUNDATION_EXPORT NSString *const kInitDataCallbackKey;
18 | FOUNDATION_EXPORT NSString *const kDisposeCallbackKey;
19 | FOUNDATION_EXPORT NSString *const kDistanceFilterKey;
20 |
21 | FOUNDATION_EXPORT NSString *const kChannelId;
22 | FOUNDATION_EXPORT NSString *const kBackgroundChannelId;
23 |
24 | FOUNDATION_EXPORT NSString *const kMethodServiceInitialized;
25 | FOUNDATION_EXPORT NSString *const kMethodPluginInitializeService;
26 | FOUNDATION_EXPORT NSString *const kMethodPluginRegisterLocationUpdate;
27 | FOUNDATION_EXPORT NSString *const kMethodPluginUnRegisterLocationUpdate;
28 | FOUNDATION_EXPORT NSString *const kMethodPluginIsRegisteredLocationUpdate;
29 | FOUNDATION_EXPORT NSString *const kMethodPluginIsServiceRunning;
30 | FOUNDATION_EXPORT NSString *const kMethodPluginUpdateNotification;
31 |
32 | FOUNDATION_EXPORT NSString *const kArgLatitude;
33 | FOUNDATION_EXPORT NSString *const kArgLongitude;
34 | FOUNDATION_EXPORT NSString *const kArgAccuracy;
35 | FOUNDATION_EXPORT NSString *const kArgAltitude;
36 | FOUNDATION_EXPORT NSString *const kArgSpeed;
37 | FOUNDATION_EXPORT NSString *const kArgSpeedAccuracy;
38 | FOUNDATION_EXPORT NSString *const kArgHeading;
39 | FOUNDATION_EXPORT NSString *const kArgTime;
40 | FOUNDATION_EXPORT NSString *const kArgCallback;
41 | FOUNDATION_EXPORT NSString *const kArgInitCallback;
42 | FOUNDATION_EXPORT NSString *const kArgInitDataCallback;
43 | FOUNDATION_EXPORT NSString *const kArgDisposeCallback;
44 | FOUNDATION_EXPORT NSString *const kArgLocation;
45 | FOUNDATION_EXPORT NSString *const kArgSettings;
46 | FOUNDATION_EXPORT NSString *const kArgCallbackDispatcher;
47 |
48 | FOUNDATION_EXPORT NSString *const kSettingsAccuracy;
49 | FOUNDATION_EXPORT NSString *const kSettingsDistanceFilter;
50 | FOUNDATION_EXPORT NSString *const kSettingsShowsBackgroundLocationIndicator;
51 |
52 | FOUNDATION_EXPORT NSString *const kBCMSendLocation;
53 | FOUNDATION_EXPORT NSString *const kBCMInit;
54 | FOUNDATION_EXPORT NSString *const kBCMDispose;
55 |
56 | FOUNDATION_EXPORT NSString *const kPrefObservingRegion;
57 | FOUNDATION_EXPORT NSString *const kPrefServiceRunning;
58 |
59 | @end
60 |
61 | NS_ASSUME_NONNULL_END
62 |
--------------------------------------------------------------------------------
/ios/Classes/Globals.m:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.m
3 | // background_locator
4 | //
5 | // Created by Mehdi Sohrabi on 6/3/20.
6 | //
7 |
8 | #import "Globals.h"
9 |
10 | @implementation Globals
11 |
12 | NSString *const kCallbackDispatcherKey = @"callback_dispatcher_handle_key";
13 | NSString *const kCallbackKey = @"callback_handle_key";
14 | NSString *const kInitCallbackKey = @"init_callback_handle_key";
15 | NSString *const kInitDataCallbackKey = @"init_data_callback_key";
16 | NSString *const kDisposeCallbackKey = @"dispose_callback_handle_key";
17 | NSString *const kDistanceFilterKey = @"distance_filter_key";
18 | NSString *const kChannelId = @"app.rekab/locator_plugin";
19 | NSString *const kBackgroundChannelId = @"app.rekab/locator_plugin_background";
20 |
21 | NSString *const kMethodServiceInitialized = @"LocatorService.initialized";
22 | NSString *const kMethodPluginInitializeService = @"LocatorPlugin.initializeService";
23 | NSString *const kMethodPluginRegisterLocationUpdate = @"LocatorPlugin.registerLocationUpdate";
24 | NSString *const kMethodPluginUnRegisterLocationUpdate = @"LocatorPlugin.unRegisterLocationUpdate";
25 | NSString *const kMethodPluginIsRegisteredLocationUpdate = @"LocatorPlugin.isRegisterLocationUpdate";
26 | NSString *const kMethodPluginIsServiceRunning = @"LocatorPlugin.isServiceRunning";
27 | NSString *const kMethodPluginUpdateNotification = @"LocatorPlugin.updateNotification";
28 |
29 | NSString *const kArgLatitude = @"latitude";
30 | NSString *const kArgLongitude = @"longitude";
31 | NSString *const kArgAccuracy = @"accuracy";
32 | NSString *const kArgAltitude = @"altitude";
33 | NSString *const kArgSpeed = @"speed";
34 | NSString *const kArgSpeedAccuracy = @"speed_accuracy";
35 | NSString *const kArgHeading = @"heading";
36 | NSString *const kArgTime = @"time";
37 | NSString *const kArgCallback = @"callback";
38 | NSString *const kArgInitCallback = @"initCallback";
39 | NSString *const kArgInitDataCallback = @"initDataCallback";
40 | NSString *const kArgDisposeCallback = @"disposeCallback";
41 | NSString *const kArgLocation = @"location";
42 | NSString *const kArgSettings = @"settings";
43 | NSString *const kArgCallbackDispatcher = @"callbackDispatcher";
44 |
45 | NSString *const kSettingsAccuracy = @"settings_accuracy";
46 | NSString *const kSettingsDistanceFilter = @"settings_distanceFilter";
47 | NSString *const kSettingsShowsBackgroundLocationIndicator = @"settings_ios_showsBackgroundLocationIndicator";
48 |
49 | NSString *const kBCMSendLocation = @"BCM_SEND_LOCATION";
50 | NSString *const kBCMInit = @"BCM_INIT";
51 | NSString *const kBCMDispose = @"BCM_DISPOSE";
52 |
53 | NSString *const kPrefObservingRegion = @"pref_observingRegion";
54 | NSString *const kPrefServiceRunning = @"pref_serviceRunning";
55 |
56 |
57 | @end
58 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/provider/LocationParserUtil.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator.provider
2 |
3 | import android.location.Location
4 | import android.os.Build
5 | import com.google.android.gms.location.LocationResult
6 | import rekab.app.background_locator.Keys
7 | import java.util.HashMap
8 |
9 | class LocationParserUtil {
10 | companion object {
11 | fun getLocationMapFromLocation(location: Location): HashMap {
12 | var speedAccuracy = 0f
13 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
14 | speedAccuracy = location.speedAccuracyMetersPerSecond
15 | }
16 | var isMocked = false
17 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
18 | isMocked = location.isFromMockProvider
19 | }
20 |
21 | return hashMapOf(
22 | Keys.ARG_IS_MOCKED to isMocked,
23 | Keys.ARG_LATITUDE to location.latitude,
24 | Keys.ARG_LONGITUDE to location.longitude,
25 | Keys.ARG_ACCURACY to location.accuracy,
26 | Keys.ARG_ALTITUDE to location.altitude,
27 | Keys.ARG_SPEED to location.speed,
28 | Keys.ARG_SPEED_ACCURACY to speedAccuracy,
29 | Keys.ARG_HEADING to location.bearing,
30 | Keys.ARG_TIME to location.time.toDouble(),
31 | Keys.ARG_PROVIDER to location.provider,
32 | )
33 | }
34 |
35 | fun getLocationMapFromLocation(location: LocationResult?): HashMap? {
36 | val firstLocation = location?.lastLocation ?: return null
37 |
38 | var speedAccuracy = 0f
39 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
40 | speedAccuracy = firstLocation.speedAccuracyMetersPerSecond
41 | }
42 | var isMocked = false
43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
44 | isMocked = firstLocation.isFromMockProvider
45 | }
46 |
47 | return hashMapOf(
48 | Keys.ARG_IS_MOCKED to isMocked,
49 | Keys.ARG_LATITUDE to firstLocation.latitude,
50 | Keys.ARG_LONGITUDE to firstLocation.longitude,
51 | Keys.ARG_ACCURACY to firstLocation.accuracy,
52 | Keys.ARG_ALTITUDE to firstLocation.altitude,
53 | Keys.ARG_SPEED to firstLocation.speed,
54 | Keys.ARG_SPEED_ACCURACY to speedAccuracy,
55 | Keys.ARG_HEADING to firstLocation.bearing,
56 | Keys.ARG_TIME to firstLocation.time.toDouble())
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/lib/utils/settings_util.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'dart:ui';
3 |
4 | import 'package:background_locator/keys.dart';
5 | import 'package:background_locator/location_dto.dart';
6 | import 'package:background_locator/settings/android_settings.dart';
7 | import 'package:background_locator/settings/ios_settings.dart';
8 |
9 | class SettingsUtil {
10 | static Map getArgumentsMap(
11 | {required void Function(LocationDto) callback,
12 | void Function(Map)? initCallback,
13 | Map? initDataCallback,
14 | void Function()? disposeCallback,
15 | AndroidSettings androidSettings = const AndroidSettings(),
16 | IOSSettings iosSettings = const IOSSettings()}) {
17 | final args = _getCommonArgumentsMap(callback: callback,
18 | initCallback: initCallback,
19 | initDataCallback: initDataCallback,
20 | disposeCallback: disposeCallback);
21 |
22 | if (Platform.isAndroid) {
23 | args.addAll(_getAndroidArgumentsMap(androidSettings));
24 | } else if (Platform.isIOS) {
25 | args.addAll(_getIOSArgumentsMap(iosSettings));
26 | }
27 |
28 | return args;
29 | }
30 |
31 | static Map _getCommonArgumentsMap({
32 | required void Function(LocationDto) callback,
33 | void Function(Map)? initCallback,
34 | Map? initDataCallback,
35 | void Function()? disposeCallback
36 | }) {
37 | final Map args = {
38 | Keys.ARG_CALLBACK:
39 | PluginUtilities.getCallbackHandle(callback)!.toRawHandle(),
40 | };
41 |
42 | if (initCallback != null) {
43 | args[Keys.ARG_INIT_CALLBACK] =
44 | PluginUtilities.getCallbackHandle(initCallback)!.toRawHandle();
45 | }
46 | if (disposeCallback != null) {
47 | args[Keys.ARG_DISPOSE_CALLBACK] =
48 | PluginUtilities.getCallbackHandle(disposeCallback)!.toRawHandle();
49 | }
50 | if (initDataCallback != null ){
51 | args[Keys.ARG_INIT_DATA_CALLBACK] = initDataCallback;
52 |
53 | }
54 |
55 | return args;
56 | }
57 |
58 | static Map _getAndroidArgumentsMap(
59 | AndroidSettings androidSettings) {
60 | final Map args = {
61 | Keys.ARG_SETTINGS: androidSettings.toMap()
62 | };
63 |
64 | if (androidSettings.androidNotificationSettings.notificationTapCallback !=
65 | null) {
66 | args[Keys.ARG_NOTIFICATION_CALLBACK] = PluginUtilities.getCallbackHandle(
67 | androidSettings
68 | .androidNotificationSettings.notificationTapCallback!)!
69 | .toRawHandle();
70 | }
71 |
72 | return args;
73 | }
74 |
75 | static Map _getIOSArgumentsMap(IOSSettings iosSettings) {
76 | return iosSettings.toMap();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lib/background_locator.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:ui';
3 |
4 | import 'package:background_locator/settings/android_settings.dart';
5 | import 'package:background_locator/settings/ios_settings.dart';
6 | import 'package:background_locator/utils/settings_util.dart';
7 | import 'package:flutter/services.dart';
8 | import 'package:flutter/widgets.dart';
9 |
10 | import 'auto_stop_handler.dart';
11 | import 'callback_dispatcher.dart';
12 | import 'keys.dart';
13 | import 'location_dto.dart';
14 |
15 | class BackgroundLocator {
16 | static const MethodChannel _channel = const MethodChannel(Keys.CHANNEL_ID);
17 |
18 | static Future initialize() async {
19 | final CallbackHandle callback =
20 | PluginUtilities.getCallbackHandle(callbackDispatcher)!;
21 | await _channel.invokeMethod(Keys.METHOD_PLUGIN_INITIALIZE_SERVICE,
22 | {Keys.ARG_CALLBACK_DISPATCHER: callback.toRawHandle()});
23 | }
24 |
25 | static Future registerLocationUpdate(
26 | void Function(LocationDto) callback,
27 | {void Function(Map)? initCallback,
28 | Map initDataCallback = const {},
29 | void Function()? disposeCallback,
30 | bool autoStop = false,
31 | AndroidSettings androidSettings = const AndroidSettings(),
32 | IOSSettings iosSettings = const IOSSettings()}) async {
33 | if (autoStop) {
34 | WidgetsBinding.instance!.addObserver(AutoStopHandler());
35 | }
36 |
37 | final args = SettingsUtil.getArgumentsMap(
38 | callback: callback,
39 | initCallback: initCallback,
40 | initDataCallback: initDataCallback,
41 | disposeCallback: disposeCallback,
42 | androidSettings: androidSettings,
43 | iosSettings: iosSettings);
44 |
45 | await _channel.invokeMethod(
46 | Keys.METHOD_PLUGIN_REGISTER_LOCATION_UPDATE, args);
47 | }
48 |
49 | static Future unRegisterLocationUpdate() async {
50 | await _channel.invokeMethod(Keys.METHOD_PLUGIN_UN_REGISTER_LOCATION_UPDATE);
51 | }
52 |
53 | static Future isRegisterLocationUpdate() async {
54 | return (await _channel
55 | .invokeMethod(Keys.METHOD_PLUGIN_IS_REGISTER_LOCATION_UPDATE))!;
56 | }
57 |
58 | static Future isServiceRunning() async {
59 | return (await _channel
60 | .invokeMethod(Keys.METHOD_PLUGIN_IS_SERVICE_RUNNING))!;
61 | }
62 |
63 | static Future updateNotificationText(
64 | {String? title, String? msg, String? bigMsg}) async {
65 | final Map arg = {};
66 |
67 | if (title != null) {
68 | arg[Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE] = title;
69 | }
70 |
71 | if (msg != null) {
72 | arg[Keys.SETTINGS_ANDROID_NOTIFICATION_MSG] = msg;
73 | }
74 |
75 | if (bigMsg != null) {
76 | arg[Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG] = bigMsg;
77 | }
78 |
79 | await _channel.invokeMethod(Keys.METHOD_PLUGIN_UPDATE_NOTIFICATION, arg);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/example/lib/location_service_repository.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:isolate';
3 | import 'dart:math';
4 | import 'dart:ui';
5 |
6 | import 'package:background_locator/location_dto.dart';
7 |
8 | import 'file_manager.dart';
9 |
10 | class LocationServiceRepository {
11 | static LocationServiceRepository _instance = LocationServiceRepository._();
12 |
13 | LocationServiceRepository._();
14 |
15 | factory LocationServiceRepository() {
16 | return _instance;
17 | }
18 |
19 | static const String isolateName = 'LocatorIsolate';
20 |
21 | int _count = -1;
22 |
23 | Future init(Map params) async {
24 | //TODO change logs
25 | print("***********Init callback handler");
26 | if (params.containsKey('countInit')) {
27 | dynamic tmpCount = params['countInit'];
28 | if (tmpCount is double) {
29 | _count = tmpCount.toInt();
30 | } else if (tmpCount is String) {
31 | _count = int.parse(tmpCount);
32 | } else if (tmpCount is int) {
33 | _count = tmpCount;
34 | } else {
35 | _count = -2;
36 | }
37 | } else {
38 | _count = 0;
39 | }
40 | print("$_count");
41 | await setLogLabel("start");
42 | final SendPort send = IsolateNameServer.lookupPortByName(isolateName);
43 | send?.send(null);
44 | }
45 |
46 | Future dispose() async {
47 | print("***********Dispose callback handler");
48 | print("$_count");
49 | await setLogLabel("end");
50 | final SendPort send = IsolateNameServer.lookupPortByName(isolateName);
51 | send?.send(null);
52 | }
53 |
54 | Future callback(LocationDto locationDto) async {
55 | print('$_count location in dart: ${locationDto.toString()}');
56 | await setLogPosition(_count, locationDto);
57 | final SendPort send = IsolateNameServer.lookupPortByName(isolateName);
58 | send?.send(locationDto);
59 | _count++;
60 |
61 | }
62 |
63 | static Future setLogLabel(String label) async {
64 | final date = DateTime.now();
65 | await FileManager.writeToLogFile(
66 | '------------\n$label: ${formatDateLog(date)}\n------------\n');
67 | }
68 |
69 | static Future setLogPosition(int count, LocationDto data) async {
70 | final date = DateTime.now();
71 | await FileManager.writeToLogFile(
72 | '$count : ${formatDateLog(date)} --> ${formatLog(data)} --- isMocked: ${data.isMocked}\n');
73 | }
74 |
75 | static double dp(double val, int places) {
76 | double mod = pow(10.0, places);
77 | return ((val * mod).round().toDouble() / mod);
78 | }
79 |
80 | static String formatDateLog(DateTime date) {
81 | return date.hour.toString() +
82 | ":" +
83 | date.minute.toString() +
84 | ":" +
85 | date.second.toString();
86 | }
87 |
88 | static String formatLog(LocationDto locationDto) {
89 | return dp(locationDto.latitude, 4).toString() +
90 | " " +
91 | dp(locationDto.longitude, 4).toString();
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
24 |
32 |
35 |
39 |
40 |
41 |
42 |
43 |
44 |
46 |
47 |
48 |
49 |
50 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/IsolateHolderExtension.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import com.google.android.gms.location.LocationRequest
6 | import io.flutter.FlutterInjector
7 | import io.flutter.embedding.engine.FlutterEngine
8 | import io.flutter.embedding.engine.dart.DartExecutor
9 | import io.flutter.plugin.common.MethodChannel
10 | import io.flutter.view.FlutterCallbackInformation
11 | import rekab.app.background_locator.provider.LocationRequestOptions
12 | import java.util.concurrent.atomic.AtomicBoolean
13 |
14 | internal fun IsolateHolderService.startLocatorService(context: Context) {
15 |
16 | val serviceStarted = AtomicBoolean(IsolateHolderService.isServiceRunning)
17 | // start synchronized block to prevent multiple service instant
18 | synchronized(serviceStarted) {
19 | this.context = context
20 | if (IsolateHolderService.backgroundEngine == null) {
21 |
22 | val callbackHandle = context.getSharedPreferences(
23 | Keys.SHARED_PREFERENCES_KEY,
24 | Context.MODE_PRIVATE)
25 | .getLong(Keys.CALLBACK_DISPATCHER_HANDLE_KEY, 0)
26 | val callbackInfo = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle)
27 |
28 | // We need flutter engine to handle callback, so if it is not available we have to create a
29 | // Flutter engine without any view
30 | IsolateHolderService.backgroundEngine = FlutterEngine(context)
31 |
32 | val args = DartExecutor.DartCallback(
33 | context.assets,
34 | FlutterInjector.instance().flutterLoader().findAppBundlePath(),
35 | callbackInfo
36 | )
37 | IsolateHolderService.backgroundEngine?.dartExecutor?.executeDartCallback(args)
38 | }
39 | }
40 |
41 | backgroundChannel =
42 | MethodChannel(IsolateHolderService.backgroundEngine?.dartExecutor?.binaryMessenger,
43 | Keys.BACKGROUND_CHANNEL_ID)
44 | backgroundChannel.setMethodCallHandler(this)
45 | }
46 |
47 | fun getLocationRequest(intent: Intent): LocationRequestOptions {
48 | val interval: Long = (intent.getIntExtra(Keys.SETTINGS_INTERVAL, 10) * 1000).toLong()
49 | val accuracyKey = intent.getIntExtra(Keys.SETTINGS_ACCURACY, 4)
50 | val accuracy = getAccuracy(accuracyKey)
51 | val distanceFilter = intent.getDoubleExtra(Keys.SETTINGS_DISTANCE_FILTER, 0.0)
52 |
53 | return LocationRequestOptions(interval, accuracy, distanceFilter.toFloat())
54 | }
55 |
56 | fun getAccuracy(key: Int): Int {
57 | return when (key) {
58 | 0 -> LocationRequest.PRIORITY_NO_POWER
59 | 1 -> LocationRequest.PRIORITY_LOW_POWER
60 | 2 -> LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY
61 | 3 -> LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY
62 | 4 -> LocationRequest.PRIORITY_HIGH_ACCURACY
63 | else -> LocationRequest.PRIORITY_HIGH_ACCURACY
64 | }
65 | }
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.6.12
2 | * Fixes onStatusChanged crash;
3 | * Fixed issue #94;
4 | * Fix importing path_provider in ios example;
5 | * Fix issue #266;
6 | * Fix Android 12 location permission handling;
7 | * Add network location provider even gps location data not update;
8 |
9 | ## 1.6.6
10 | * Fix invoking method on flutter channel when engine is not ready; (#254)
11 |
12 | ## 1.6.5
13 | * Fix returning result for unRegisterPlugin method on Android; (#262)
14 |
15 | ## 1.6.4
16 | * Fix triggering location update when plugin is stopped; (#258)
17 | * Fix saving service start stop status; (#259)
18 |
19 | ## 1.6.3
20 | * Bug fixes;
21 |
22 | ## 1.6.2+1-beta
23 | * Bring back init and dispose callback;
24 |
25 | ## 1.6.1+1-beta
26 | * Fix crash on Android location client causing by change in status of location provider;
27 |
28 | ## 1.6.0+2-beta
29 | * Fix crash on start;
30 |
31 | ## 1.6.0+1-beta
32 | * Use new flutter engine;
33 | * Fix start stop bug which prevents correct state in plugin;
34 |
35 | ## 1.5.0+1
36 | * Add null safety support;
37 |
38 | ## 1.4.0+1
39 | * Set default value for autoStop;
40 | * Fix register and unregister futures never complete on Android;
41 | * Fix Doze problem for Android >= 10;
42 |
43 | ## 1.3.2+1
44 | * Fix compile error on sdk 30;
45 | * Fix app stop locating on android sdk 30 in background;
46 |
47 | ## 1.3.0+1
48 | * Add google location client as option;
49 | * Several bug fixes;
50 |
51 | ## 1.2.2+1
52 | * Add platform specific settings;
53 | * Add ability to update android notification;
54 | * Ability to showsBackgroundLocationIndicator on iOS;
55 |
56 | ## 1.1.13+1
57 | * add isServiceRunning method;
58 |
59 | ## 1.1.12+1
60 | * Added support for big text in Android notification;
61 |
62 | ## 1.1.11+1
63 | * Fix getCallbackHandle bug which caused some callbacks not getting executed;
64 |
65 | ## 1.1.10+1
66 | * Add region monitoring for iOS to get location info while app is terminated;
67 | * Minor iOS bug fix;
68 | * Add a way to use 3rd party plugins while app is terminated in iOS;
69 |
70 | ## 1.1.7+1
71 | * Add notification icon color;
72 | * Add isMocked property on location model;
73 | * Add channel name property on location dto;
74 |
75 | ## 1.1.5+1
76 | * Fix crash in onStartCommand caused by null intent on Android;
77 | * Fix getting several unwanted position on iOS;
78 |
79 | ## 1.1.3+1
80 | * Add possibility to restart locator service after reboot;
81 | * Fix triggering android notification callback with wrong notification;
82 |
83 | ## 1.1.2+2
84 | * Fix optional android notification callback.
85 |
86 | ## 1.1.2+1
87 | * Fix accessing other plugins when app is terminated.
88 |
89 | ## 1.1.1+1
90 | * Fix Callback is not triggered in iOS.
91 |
92 | ## 1.1.0+1
93 | * Add callback for android notification.
94 |
95 | ## 1.0.1+2
96 | * Fix crash on detach.
97 |
98 | ## 1.0.1+1
99 | * Add isRegistered method.
100 | * Bug fixes.
101 |
102 | ## 1.0.0+1
103 | * Add auto stop feature.
104 | * Update flutter plugin library to version 2.
105 |
106 | ## 0.0.4-beta
107 | * Add parameter to setting to change android wakelock time.
108 | * Prevent service from registering twice.
109 |
110 | ## 0.0.3-beta
111 | Change where location access requested.
112 |
113 | ## 0.0.2-beta
114 |
115 | * Improvements.
116 |
117 | ## 0.0.1-beta
118 |
119 | * initial release.
120 |
--------------------------------------------------------------------------------
/lib/keys.dart:
--------------------------------------------------------------------------------
1 | class Keys {
2 | static const String CHANNEL_ID = 'app.rekab/locator_plugin';
3 | static const String BACKGROUND_CHANNEL_ID =
4 | 'app.rekab/locator_plugin_background';
5 |
6 | static const String METHOD_SERVICE_INITIALIZED = 'LocatorService.initialized';
7 | static const String METHOD_PLUGIN_INITIALIZE_SERVICE =
8 | 'LocatorPlugin.initializeService';
9 | static const String METHOD_PLUGIN_REGISTER_LOCATION_UPDATE =
10 | 'LocatorPlugin.registerLocationUpdate';
11 | static const String METHOD_PLUGIN_UN_REGISTER_LOCATION_UPDATE =
12 | 'LocatorPlugin.unRegisterLocationUpdate';
13 | static const String METHOD_PLUGIN_IS_REGISTER_LOCATION_UPDATE =
14 | 'LocatorPlugin.isRegisterLocationUpdate';
15 | static const String METHOD_PLUGIN_IS_SERVICE_RUNNING =
16 | 'LocatorPlugin.isServiceRunning';
17 | static const String METHOD_PLUGIN_UPDATE_NOTIFICATION =
18 | 'LocatorPlugin.updateNotification';
19 |
20 | static const String ARG_IS_MOCKED = 'is_mocked';
21 | static const String ARG_LATITUDE = 'latitude';
22 | static const String ARG_LONGITUDE = 'longitude';
23 | static const String ARG_ALTITUDE = 'altitude';
24 | static const String ARG_ACCURACY = 'accuracy';
25 | static const String ARG_SPEED = 'speed';
26 | static const String ARG_SPEED_ACCURACY = 'speed_accuracy';
27 | static const String ARG_HEADING = 'heading';
28 | static const String ARG_TIME = 'time';
29 | static const String ARG_PROVIDER = 'provider';
30 | static const String ARG_CALLBACK = 'callback';
31 | static const String ARG_NOTIFICATION_CALLBACK = 'notificationCallback';
32 | static const String ARG_INIT_CALLBACK = 'initCallback';
33 | static const String ARG_INIT_DATA_CALLBACK = 'initDataCallback';
34 | static const String ARG_DISPOSE_CALLBACK = 'disposeCallback';
35 | static const String ARG_LOCATION = 'location';
36 | static const String ARG_SETTINGS = 'settings';
37 | static const String ARG_CALLBACK_DISPATCHER = 'callbackDispatcher';
38 |
39 | static const String SETTINGS_ACCURACY = 'settings_accuracy';
40 | static const String SETTINGS_INTERVAL = 'settings_interval';
41 | static const String SETTINGS_DISTANCE_FILTER = 'settings_distanceFilter';
42 | static const String SETTINGS_AUTO_STOP = 'settings_autoStop';
43 | static const String SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME =
44 | 'settings_android_notificationChannelName';
45 | static const String SETTINGS_ANDROID_NOTIFICATION_TITLE =
46 | 'settings_android_notificationTitle';
47 | static const String SETTINGS_ANDROID_NOTIFICATION_MSG =
48 | 'settings_android_notificationMsg';
49 | static const String SETTINGS_ANDROID_NOTIFICATION_BIG_MSG =
50 | 'settings_android_notificationBigMsg';
51 | static const String SETTINGS_ANDROID_NOTIFICATION_ICON =
52 | 'settings_android_notificationIcon';
53 | static const String SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR =
54 | 'settings_android_notificationIconColor';
55 | static const String SETTINGS_ANDROID_WAKE_LOCK_TIME =
56 | 'settings_android_wakeLockTime';
57 | static const String SETTINGS_ANDROID_LOCATION_CLIENT =
58 | "settings_android_location_client";
59 |
60 | static const String SETTINGS_IOS_SHOWS_BACKGROUND_LOCATION_INDICATOR =
61 | 'settings_ios_showsBackgroundLocationIndicator';
62 |
63 | static const String BCM_SEND_LOCATION = 'BCM_SEND_LOCATION';
64 | static const String BCM_NOTIFICATION_CLICK = 'BCM_NOTIFICATION_CLICK';
65 | static const String BCM_INIT = 'BCM_INIT';
66 | static const String BCM_DISPOSE = 'BCM_DISPOSE';
67 | }
68 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/provider/AndroidLocationProviderClient.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator.provider
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.location.Location
6 | import android.location.LocationListener
7 | import android.location.LocationManager
8 | import androidx.core.content.ContextCompat
9 | import android.os.Bundle
10 |
11 | class AndroidLocationProviderClient(context: Context, override var listener: LocationUpdateListener?) : BLLocationProvider, LocationListener {
12 | private val client: LocationManager? =
13 | ContextCompat.getSystemService(context, LocationManager::class.java)
14 |
15 | private var overrideLocation: Boolean = false
16 | private var timeOfLastLocation: Long = 0L
17 | private var timeBetweenLocation: Long = 0L
18 |
19 | @SuppressLint("MissingPermission")
20 | override fun removeLocationUpdates() {
21 | client?.removeUpdates(this)
22 | }
23 |
24 | @SuppressLint("MissingPermission")
25 | override fun requestLocationUpdates(request: LocationRequestOptions) {
26 | var gpsLocation: Location? = null
27 | var networkLocation: Location? = null
28 | timeBetweenLocation = request.interval
29 | if (client?.isProviderEnabled(LocationManager.GPS_PROVIDER) == true) {
30 | client.requestLocationUpdates(LocationManager.GPS_PROVIDER,
31 | request.interval,
32 | request.distanceFilter,
33 | this)
34 | }
35 | if (client?.isProviderEnabled(LocationManager.NETWORK_PROVIDER) == true) {
36 | client.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
37 | request.interval,
38 | request.distanceFilter,
39 | this)
40 | }
41 | gpsLocation = client?.getLastKnownLocation(LocationManager.GPS_PROVIDER)
42 | networkLocation = client?.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
43 | // return the android device last Location after start request location
44 | if (gpsLocation != null && networkLocation != null) {
45 | if (gpsLocation.time < networkLocation.time) {
46 | onLocationChanged(networkLocation)
47 | } else {
48 | onLocationChanged(gpsLocation)
49 | }
50 | } else if (gpsLocation != null) {
51 | onLocationChanged(gpsLocation)
52 | } else if (networkLocation != null) {
53 | onLocationChanged(networkLocation)
54 | }
55 | }
56 |
57 | override fun onLocationChanged(location: Location) {
58 | overrideLocation = false
59 | //whenever the expected time period is reached invalidate the last known accuracy
60 | // so that we don't just receive better and better accuracy and eventually risk receiving
61 | // only minimal locations
62 | if (location.hasAccuracy()) {
63 | if (!location.accuracy.isNaN() &&
64 | location.accuracy != 0.0f &&
65 | !location.accuracy.isFinite() &&
66 | !location.accuracy.isInfinite()) {
67 | overrideLocation = true
68 | }
69 | }
70 | //ensure that we don't get a lot of events
71 | // or if enabled, only get more accurate events within mTimeBetweenLocationEvents
72 | if (location.time - timeOfLastLocation >= timeBetweenLocation || overrideLocation) {
73 | //be sure to store the time of receiving this event !
74 | timeOfLastLocation = location.time
75 | //send message to parent containing the location object
76 | listener?.onLocationUpdated(LocationParserUtil.getLocationMapFromLocation(location))
77 | }
78 | }
79 |
80 | override fun onProviderDisabled(provider: String) {
81 | // nop
82 | }
83 |
84 | override fun onProviderEnabled(provider: String) {
85 | // nop
86 | }
87 | override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
88 |
89 | }
--------------------------------------------------------------------------------
/lib/settings/android_settings.dart:
--------------------------------------------------------------------------------
1 | import 'package:background_locator/keys.dart';
2 | import 'package:background_locator/settings/locator_settings.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | enum LocationClient { google, android }
6 |
7 | class AndroidNotificationSettings {
8 | final String notificationChannelName;
9 | final String notificationTitle;
10 | final String notificationMsg;
11 | final String notificationBigMsg;
12 | final String notificationIcon;
13 | final Color notificationIconColor;
14 | final VoidCallback? notificationTapCallback;
15 |
16 | /// [notificationTitle] Title of the notification. Only applies for android. Default is 'Start Location Tracking'.
17 | ///
18 | /// [notificationMsg] Message of notification. Only applies for android. Default is 'Track location in background'.
19 | ///
20 | /// [notificationBigMsg] Message to be displayed in the expanded content area of the notification. Only applies for android. Default is 'Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running.'.
21 | ///
22 | /// [notificationIcon] Icon name for notification. Only applies for android. The icon should be in 'mipmap' Directory.
23 | /// Default is app icon. Icon must comply to android rules to be displayed (transparent background and black/white shape)
24 | ///
25 | /// [notificationIconColor] Icon color for notification from notification drawer. Only applies for android. Default color is grey.
26 | ///
27 | /// [notificationTapCallback] callback for notification tap
28 | ///
29 | const AndroidNotificationSettings(
30 | {this.notificationChannelName = 'Location tracking',
31 | this.notificationTitle = 'Start Location Tracking',
32 | this.notificationMsg = 'Track location in background',
33 | this.notificationBigMsg =
34 | 'Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running.',
35 | this.notificationIcon = '',
36 | this.notificationIconColor = Colors.grey,
37 | this.notificationTapCallback});
38 | }
39 |
40 | class AndroidSettings extends LocatorSettings {
41 | final AndroidNotificationSettings androidNotificationSettings;
42 | final int wakeLockTime;
43 | final int interval;
44 | final LocationClient client;
45 |
46 | /// [accuracy] The accuracy of location, Default is max accuracy NAVIGATION.
47 | ///
48 | /// [interval] Interval of retrieving location update in second. Only applies for android. Default is 5 second.
49 | ///
50 | /// [distanceFilter] distance in meter to trigger location update, Default is 0 meter.
51 | ///
52 | /// [androidNotificationSettings] Specific setting for android notification.
53 | ///
54 | /// [wakeLockTime] Time for living service in background in minutes. Only applies in android. Default is 60 minute.
55 | const AndroidSettings(
56 | {LocationAccuracy accuracy = LocationAccuracy.NAVIGATION,
57 | this.interval = 5,
58 | double distanceFilter = 0,
59 | this.androidNotificationSettings = const AndroidNotificationSettings(),
60 | this.wakeLockTime = 60,
61 | this.client = LocationClient.google})
62 | : super(accuracy: accuracy, distanceFilter: distanceFilter);
63 |
64 | Map toMap() {
65 | return {
66 | Keys.SETTINGS_ACCURACY: accuracy.value,
67 | Keys.SETTINGS_INTERVAL: interval,
68 | Keys.SETTINGS_DISTANCE_FILTER: distanceFilter,
69 | Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME: wakeLockTime,
70 | Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME:
71 | androidNotificationSettings.notificationChannelName,
72 | Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE:
73 | androidNotificationSettings.notificationTitle,
74 | Keys.SETTINGS_ANDROID_NOTIFICATION_MSG:
75 | androidNotificationSettings.notificationMsg,
76 | Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG:
77 | androidNotificationSettings.notificationBigMsg,
78 | Keys.SETTINGS_ANDROID_NOTIFICATION_ICON:
79 | androidNotificationSettings.notificationIcon,
80 | Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR:
81 | androidNotificationSettings.notificationIconColor.value,
82 | Keys.SETTINGS_ANDROID_LOCATION_CLIENT: client.index
83 | };
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/Keys.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator
2 |
3 | class Keys {
4 | companion object {
5 | @JvmStatic
6 | val SHARED_PREFERENCES_KEY = "SHARED_PREFERENCES_KEY"
7 |
8 | @JvmStatic
9 | val CALLBACK_DISPATCHER_HANDLE_KEY = "CALLBACK_DISPATCHER_HANDLE_KEY"
10 |
11 | @JvmStatic
12 | val CALLBACK_HANDLE_KEY = "CALLBACK_HANDLE_KEY"
13 |
14 | @JvmStatic
15 | val NOTIFICATION_CALLBACK_HANDLE_KEY = "NOTIFICATION_CALLBACK_HANDLE_KEY"
16 |
17 | @JvmStatic
18 | val INIT_CALLBACK_HANDLE_KEY = "INIT_CALLBACK_HANDLE_KEY"
19 |
20 | @JvmStatic
21 | val INIT_DATA_CALLBACK_KEY = "INIT_DATA_CALLBACK_KEY"
22 |
23 | @JvmStatic
24 | val DISPOSE_CALLBACK_HANDLE_KEY = "DISPOSE_CALLBACK_HANDLE_KEY"
25 |
26 | @JvmStatic
27 | val CHANNEL_ID = "app.rekab/locator_plugin"
28 |
29 | @JvmStatic
30 | val BACKGROUND_CHANNEL_ID = "app.rekab/locator_plugin_background"
31 |
32 | @JvmStatic
33 | val METHOD_SERVICE_INITIALIZED = "LocatorService.initialized"
34 |
35 | @JvmStatic
36 | val METHOD_PLUGIN_INITIALIZE_SERVICE = "LocatorPlugin.initializeService"
37 |
38 | @JvmStatic
39 | val METHOD_PLUGIN_REGISTER_LOCATION_UPDATE = "LocatorPlugin.registerLocationUpdate"
40 |
41 | @JvmStatic
42 | val METHOD_PLUGIN_UN_REGISTER_LOCATION_UPDATE = "LocatorPlugin.unRegisterLocationUpdate"
43 |
44 | @JvmStatic
45 | val METHOD_PLUGIN_IS_REGISTER_LOCATION_UPDATE = "LocatorPlugin.isRegisterLocationUpdate"
46 |
47 | @JvmStatic
48 | val METHOD_PLUGIN_IS_SERVICE_RUNNING = "LocatorPlugin.isServiceRunning"
49 |
50 | @JvmStatic
51 | val METHOD_PLUGIN_UPDATE_NOTIFICATION = "LocatorPlugin.updateNotification"
52 |
53 | @JvmStatic
54 | val ARG_INIT_CALLBACK = "initCallback"
55 |
56 | @JvmStatic
57 | val ARG_INIT_DATA_CALLBACK = "initDataCallback"
58 |
59 | @JvmStatic
60 | val ARG_DISPOSE_CALLBACK = "disposeCallback"
61 |
62 | @JvmStatic
63 | val ARG_IS_MOCKED = "is_mocked"
64 |
65 | @JvmStatic
66 | val ARG_LATITUDE = "latitude"
67 |
68 | @JvmStatic
69 | val ARG_LONGITUDE = "longitude"
70 |
71 | @JvmStatic
72 | val ARG_ACCURACY = "accuracy"
73 |
74 | @JvmStatic
75 | val ARG_ALTITUDE = "altitude"
76 |
77 | @JvmStatic
78 | val ARG_SPEED = "speed"
79 |
80 | @JvmStatic
81 | val ARG_SPEED_ACCURACY = "speed_accuracy"
82 |
83 | @JvmStatic
84 | val ARG_HEADING = "heading"
85 |
86 | @JvmStatic
87 | val ARG_TIME = "time"
88 |
89 | @JvmStatic
90 | val ARG_PROVIDER = "provider"
91 |
92 | @JvmStatic
93 | val ARG_CALLBACK = "callback"
94 |
95 | @JvmStatic
96 | val ARG_NOTIFICATION_CALLBACK = "notificationCallback"
97 |
98 | @JvmStatic
99 | val ARG_LOCATION = "location"
100 |
101 | @JvmStatic
102 | val ARG_SETTINGS = "settings"
103 |
104 | @JvmStatic
105 | val ARG_CALLBACK_DISPATCHER = "callbackDispatcher"
106 |
107 |
108 | @JvmStatic
109 | val SETTINGS_ACCURACY = "settings_accuracy"
110 |
111 | @JvmStatic
112 | val SETTINGS_INTERVAL = "settings_interval"
113 |
114 | @JvmStatic
115 | val SETTINGS_DISTANCE_FILTER = "settings_distanceFilter"
116 |
117 | @JvmStatic
118 | val SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME = "settings_android_notificationChannelName"
119 |
120 | @JvmStatic
121 | val SETTINGS_ANDROID_NOTIFICATION_TITLE = "settings_android_notificationTitle"
122 |
123 | @JvmStatic
124 | val SETTINGS_ANDROID_NOTIFICATION_MSG = "settings_android_notificationMsg"
125 |
126 | @JvmStatic
127 | val SETTINGS_ANDROID_NOTIFICATION_BIG_MSG = "settings_android_notificationBigMsg"
128 |
129 | @JvmStatic
130 | val SETTINGS_ANDROID_NOTIFICATION_ICON = "settings_android_notificationIcon"
131 |
132 | @JvmStatic
133 | val SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR = "settings_android_notificationIconColor"
134 |
135 | @JvmStatic
136 | val SETTINGS_ANDROID_WAKE_LOCK_TIME = "settings_android_wakeLockTime"
137 |
138 | @JvmStatic
139 | val SETTINGS_ANDROID_LOCATION_CLIENT = "settings_android_location_client"
140 |
141 | @JvmStatic
142 | val SETTINGS_INIT_PLUGGABLE = "settings_init_pluggable"
143 |
144 | @JvmStatic
145 | val SETTINGS_DISPOSABLE_PLUGGABLE = "settings_disposable_pluggable"
146 |
147 | @JvmStatic
148 | val BCM_SEND_LOCATION = "BCM_SEND_LOCATION"
149 |
150 | @JvmStatic
151 | val BCM_NOTIFICATION_CLICK = "BCM_NOTIFICATION_CLICK"
152 |
153 | @JvmStatic
154 | val BCM_INIT = "BCM_INIT"
155 |
156 | @JvmStatic
157 | val BCM_DISPOSE = "BCM_DISPOSE"
158 |
159 | @JvmStatic
160 | val NOTIFICATION_ACTION = "com.rekab.background_locator.notification"
161 | }
162 | }
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:ffi';
3 | import 'dart:isolate';
4 | import 'dart:ui';
5 |
6 | import 'package:background_locator/background_locator.dart';
7 | import 'package:background_locator/location_dto.dart';
8 | import 'package:background_locator/settings/android_settings.dart';
9 | import 'package:background_locator/settings/ios_settings.dart';
10 | import 'package:background_locator/settings/locator_settings.dart';
11 | import 'package:flutter/material.dart';
12 | import 'package:location_permissions/location_permissions.dart';
13 |
14 | import 'file_manager.dart';
15 | import 'location_callback_handler.dart';
16 | import 'location_service_repository.dart';
17 |
18 | void main() => runApp(MyApp());
19 |
20 | class MyApp extends StatefulWidget {
21 | @override
22 | _MyAppState createState() => _MyAppState();
23 | }
24 |
25 | class _MyAppState extends State {
26 | ReceivePort port = ReceivePort();
27 |
28 | String logStr = '';
29 | bool isRunning;
30 | LocationDto lastLocation;
31 |
32 | @override
33 | void initState() {
34 | super.initState();
35 |
36 | if (IsolateNameServer.lookupPortByName(
37 | LocationServiceRepository.isolateName) !=
38 | null) {
39 | IsolateNameServer.removePortNameMapping(
40 | LocationServiceRepository.isolateName);
41 | }
42 |
43 | IsolateNameServer.registerPortWithName(
44 | port.sendPort, LocationServiceRepository.isolateName);
45 |
46 | port.listen(
47 | (dynamic data) async {
48 | await updateUI(data);
49 | },
50 | );
51 | initPlatformState();
52 | }
53 |
54 | @override
55 | void dispose() {
56 | super.dispose();
57 | }
58 |
59 | Future updateUI(LocationDto data) async {
60 | final log = await FileManager.readLogFile();
61 |
62 | await _updateNotificationText(data);
63 |
64 | setState(() {
65 | if (data != null) {
66 | lastLocation = data;
67 | }
68 | logStr = log;
69 | });
70 | }
71 |
72 | Future _updateNotificationText(LocationDto data) async {
73 | if (data == null) {
74 | return;
75 | }
76 |
77 | await BackgroundLocator.updateNotificationText(
78 | title: "new location received",
79 | msg: "${DateTime.now()}",
80 | bigMsg: "${data.latitude}, ${data.longitude}");
81 | }
82 |
83 | Future initPlatformState() async {
84 | print('Initializing...');
85 | await BackgroundLocator.initialize();
86 | logStr = await FileManager.readLogFile();
87 | print('Initialization done');
88 | final _isRunning = await BackgroundLocator.isServiceRunning();
89 | setState(() {
90 | isRunning = _isRunning;
91 | });
92 | print('Running ${isRunning.toString()}');
93 | }
94 |
95 | @override
96 | Widget build(BuildContext context) {
97 | final start = SizedBox(
98 | width: double.maxFinite,
99 | child: ElevatedButton(
100 | child: Text('Start'),
101 | onPressed: () {
102 | _onStart();
103 | },
104 | ),
105 | );
106 | final stop = SizedBox(
107 | width: double.maxFinite,
108 | child: ElevatedButton(
109 | child: Text('Stop'),
110 | onPressed: () {
111 | onStop();
112 | },
113 | ),
114 | );
115 | final clear = SizedBox(
116 | width: double.maxFinite,
117 | child: ElevatedButton(
118 | child: Text('Clear Log'),
119 | onPressed: () {
120 | FileManager.clearLogFile();
121 | setState(() {
122 | logStr = '';
123 | });
124 | },
125 | ),
126 | );
127 | String msgStatus = "-";
128 | if (isRunning != null) {
129 | if (isRunning) {
130 | msgStatus = 'Is running';
131 | } else {
132 | msgStatus = 'Is not running';
133 | }
134 | }
135 | final status = Text("Status: $msgStatus");
136 |
137 | final log = Text(
138 | logStr,
139 | );
140 |
141 | return MaterialApp(
142 | home: Scaffold(
143 | appBar: AppBar(
144 | title: const Text('Flutter background Locator'),
145 | ),
146 | body: Container(
147 | width: double.maxFinite,
148 | padding: const EdgeInsets.all(22),
149 | child: SingleChildScrollView(
150 | child: Column(
151 | crossAxisAlignment: CrossAxisAlignment.center,
152 | children: [start, stop, clear, status, log],
153 | ),
154 | ),
155 | ),
156 | ),
157 | );
158 | }
159 |
160 | void onStop() async {
161 | await BackgroundLocator.unRegisterLocationUpdate();
162 | final _isRunning = await BackgroundLocator.isServiceRunning();
163 | setState(() {
164 | isRunning = _isRunning;
165 | });
166 | }
167 |
168 | void _onStart() async {
169 | if (await _checkLocationPermission()) {
170 | await _startLocator();
171 | final _isRunning = await BackgroundLocator.isServiceRunning();
172 |
173 | setState(() {
174 | isRunning = _isRunning;
175 | lastLocation = null;
176 | });
177 | } else {
178 | // show error
179 | }
180 | }
181 |
182 | Future _checkLocationPermission() async {
183 | final access = await LocationPermissions().checkPermissionStatus();
184 | switch (access) {
185 | case PermissionStatus.unknown:
186 | case PermissionStatus.denied:
187 | case PermissionStatus.restricted:
188 | final permission = await LocationPermissions().requestPermissions(
189 | permissionLevel: LocationPermissionLevel.locationAlways,
190 | );
191 | if (permission == PermissionStatus.granted) {
192 | return true;
193 | } else {
194 | return false;
195 | }
196 | break;
197 | case PermissionStatus.granted:
198 | return true;
199 | break;
200 | default:
201 | return false;
202 | break;
203 | }
204 | }
205 |
206 | Future _startLocator() async{
207 | Map data = {'countInit': 1};
208 | return await BackgroundLocator.registerLocationUpdate(LocationCallbackHandler.callback,
209 | initCallback: LocationCallbackHandler.initCallback,
210 | initDataCallback: data,
211 | disposeCallback: LocationCallbackHandler.disposeCallback,
212 | iosSettings: IOSSettings(
213 | accuracy: LocationAccuracy.NAVIGATION, distanceFilter: 0),
214 | autoStop: false,
215 | androidSettings: AndroidSettings(
216 | accuracy: LocationAccuracy.NAVIGATION,
217 | interval: 5,
218 | distanceFilter: 0,
219 | client: LocationClient.google,
220 | androidNotificationSettings: AndroidNotificationSettings(
221 | notificationChannelName: 'Location tracking',
222 | notificationTitle: 'Start Location Tracking',
223 | notificationMsg: 'Track location in background',
224 | notificationBigMsg:
225 | 'Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running.',
226 | notificationIconColor: Colors.grey,
227 | notificationTapCallback:
228 | LocationCallbackHandler.notificationCallback)));
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | #########################
2 | # .gitignore file for Xcode4 and Xcode5 Source projects
3 | #
4 | # Apple bugs, waiting for Apple to fix/respond:
5 | #
6 | # 15564624 - what does the xccheckout file in Xcode5 do? Where's the documentation?
7 | #
8 | # Version 2.6
9 | # For latest version, see: http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects
10 | #
11 | # 2015 updates:
12 | # - Fixed typo in "xccheckout" line - thanks to @lyck for pointing it out!
13 | # - Fixed the .idea optional ignore. Thanks to @hashier for pointing this out
14 | # - Finally added "xccheckout" to the ignore. Apple still refuses to answer support requests about this, but in practice it seems you should ignore it.
15 | # - minor tweaks from Jona and Coeur (slightly more precise xc* filtering/names)
16 | # 2014 updates:
17 | # - appended non-standard items DISABLED by default (uncomment if you use those tools)
18 | # - removed the edit that an SO.com moderator made without bothering to ask me
19 | # - researched CocoaPods .lock more carefully, thanks to Gokhan Celiker
20 | # 2013 updates:
21 | # - fixed the broken "save personal Schemes"
22 | # - added line-by-line explanations for EVERYTHING (some were missing)
23 | #
24 | # NB: if you are storing "built" products, this WILL NOT WORK,
25 | # and you should use a different .gitignore (or none at all)
26 | # This file is for SOURCE projects, where there are many extra
27 | # files that we want to exclude
28 | #
29 | #########################
30 |
31 | #####
32 | # OS X temporary files that should never be committed
33 | #
34 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html
35 |
36 | .DS_Store
37 |
38 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html
39 |
40 | .Trashes
41 |
42 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html
43 |
44 | *.swp
45 |
46 | #
47 | # *.lock - this is used and abused by many editors for many different things.
48 | # For the main ones I use (e.g. Eclipse), it should be excluded
49 | # from source-control, but YMMV.
50 | # (lock files are usually local-only file-synchronization on the local FS that should NOT go in git)
51 | # c.f. the "OPTIONAL" section at bottom though, for tool-specific variations!
52 | #
53 | # In particular, if you're using CocoaPods, you'll want to comment-out this line:
54 | #.lock
55 |
56 |
57 | #
58 | # profile - REMOVED temporarily (on double-checking, I can't find it in OS X docs?)
59 | #profile
60 |
61 |
62 | ####
63 | # Xcode temporary files that should never be committed
64 | #
65 | # NB: NIB/XIB files still exist even on Storyboard projects, so we want this...
66 |
67 | #*~.nib
68 |
69 |
70 | ####
71 | # Xcode build files -
72 | #
73 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "DerivedData"
74 |
75 | DerivedData/
76 |
77 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "build"
78 |
79 | build/
80 | Build/
81 | Index/
82 | Pods/
83 |
84 |
85 | #####
86 | # Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups)
87 | #
88 | # This is complicated:
89 | #
90 | # SOMETIMES you need to put this file in version control.
91 | # Apple designed it poorly - if you use "custom executables", they are
92 | # saved in this file.
93 | # 99% of projects do NOT use those, so they do NOT want to version control this file.
94 | # ..but if you're in the 1%, comment out the line "*.pbxuser"
95 |
96 | # .pbxuser: http://lists.apple.com/archives/xcode-users/2004/Jan/msg00193.html
97 |
98 | *.pbxuser
99 |
100 | # .mode1v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html
101 |
102 | *.mode1v3
103 |
104 | # .mode2v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html
105 |
106 | *.mode2v3
107 |
108 | # .perspectivev3: http://stackoverflow.com/questions/5223297/xcode-projects-what-is-a-perspectivev3-file
109 |
110 | *.perspectivev3
111 |
112 | # NB: also, whitelist the default ones, some projects need to use these
113 | !default.pbxuser
114 | !default.mode1v3
115 | !default.mode2v3
116 | !default.perspectivev3
117 |
118 |
119 | ####
120 | # Xcode 4 - semi-personal settings
121 | #
122 | # Apple Shared data that Apple put in the wrong folder
123 | # c.f. http://stackoverflow.com/a/19260712/153422
124 | # FROM ANSWER: Apple says "don't ignore it"
125 | # FROM COMMENTS: Apple is wrong; Apple code is too buggy to trust; there are no known negative side-effects to ignoring Apple's unofficial advice and instead doing the thing that actively fixes bugs in Xcode
126 | # Up to you, but ... current advice: ignore it.
127 | *.xccheckout
128 |
129 | #
130 | #
131 | # OPTION 1: ---------------------------------
132 | # throw away ALL personal settings (including custom schemes!
133 | # - unless they are "shared")
134 | # As per build/ and DerivedData/, this ought to have a trailing slash
135 | #
136 | # NB: this is exclusive with OPTION 2 below
137 | xcuserdata/
138 |
139 | # OPTION 2: ---------------------------------
140 | # get rid of ALL personal settings, but KEEP SOME OF THEM
141 | # - NB: you must manually uncomment the bits you want to keep
142 | #
143 | # NB: this *requires* git v1.8.2 or above; you may need to upgrade to latest OS X,
144 | # or manually install git over the top of the OS X version
145 | # NB: this is exclusive with OPTION 1 above
146 | #
147 | #xcuserdata/**/*
148 |
149 | # (requires option 2 above): Personal Schemes
150 | #
151 | #!xcuserdata/**/xcschemes/*
152 |
153 | ####
154 | # XCode 4 workspaces - more detailed
155 | #
156 | # Workspaces are important! They are a core feature of Xcode - don't exclude them :)
157 | #
158 | # Workspace layout is quite spammy. For reference:
159 | #
160 | # /(root)/
161 | # /(project-name).xcodeproj/
162 | # project.pbxproj
163 | # /project.xcworkspace/
164 | # contents.xcworkspacedata
165 | # /xcuserdata/
166 | # /(your name)/xcuserdatad/
167 | # UserInterfaceState.xcuserstate
168 | # /xcshareddata/
169 | # /xcschemes/
170 | # (shared scheme name).xcscheme
171 | # /xcuserdata/
172 | # /(your name)/xcuserdatad/
173 | # (private scheme).xcscheme
174 | # xcschememanagement.plist
175 | #
176 | #
177 |
178 | ####
179 | # Xcode 4 - Deprecated classes
180 | #
181 | # Allegedly, if you manually "deprecate" your classes, they get moved here.
182 | #
183 | # We're using source-control, so this is a "feature" that we do not want!
184 |
185 | *.moved-aside
186 |
187 | ####
188 | # OPTIONAL: Some well-known tools that people use side-by-side with Xcode / iOS development
189 | #
190 | # NB: I'd rather not include these here, but gitignore's design is weak and doesn't allow
191 | # modular gitignore: you have to put EVERYTHING in one file.
192 | #
193 | # COCOAPODS:
194 | #
195 | # c.f. http://guides.cocoapods.org/using/using-cocoapods.html#what-is-a-podfilelock
196 | # c.f. http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
197 | #
198 | #!Podfile.lock
199 | #
200 | # RUBY:
201 | #
202 | # c.f. http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
203 | #
204 | #!Gemfile.lock
205 | #
206 | # IDEA:
207 | #
208 | # c.f. https://www.jetbrains.com/objc/help/managing-projects-under-version-control.html?search=workspace.xml
209 | #
210 | #.idea/workspace.xml
211 | #
212 | # TEXTMATE:
213 | #
214 | # -- UNVERIFIED: c.f. http://stackoverflow.com/a/50283/153422
215 | #
216 | #tm_build_errors
217 |
218 | ####
219 | # UNKNOWN: recommended by others, but I can't discover what these files are
220 | #
221 | RekabNetwork
222 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/rekab/app/background_locator/PreferencesManager.kt:
--------------------------------------------------------------------------------
1 | package rekab.app.background_locator
2 |
3 | import android.content.Context
4 | import com.google.gson.Gson
5 | import com.google.gson.reflect.TypeToken
6 | import rekab.app.background_locator.provider.LocationClient
7 |
8 | class PreferencesManager {
9 | companion object {
10 | private const val PREF_NAME = "background_locator"
11 |
12 | @JvmStatic
13 | fun saveCallbackDispatcher(context: Context, map: Map) {
14 | val sharedPreferences =
15 | context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
16 |
17 | sharedPreferences.edit()
18 | .putLong(Keys.ARG_CALLBACK_DISPATCHER,
19 | map[Keys.ARG_CALLBACK_DISPATCHER] as Long)
20 | .apply()
21 | }
22 |
23 | @JvmStatic
24 | fun saveSettings(context: Context, map: Map) {
25 | val sharedPreferences =
26 | context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
27 |
28 | sharedPreferences.edit()
29 | .putLong(Keys.ARG_CALLBACK,
30 | map[Keys.ARG_CALLBACK] as Long)
31 | .apply()
32 |
33 | if (map[Keys.ARG_NOTIFICATION_CALLBACK] as? Long != null) {
34 | sharedPreferences.edit()
35 | .putLong(Keys.ARG_NOTIFICATION_CALLBACK,
36 | map[Keys.ARG_NOTIFICATION_CALLBACK] as Long)
37 | .apply()
38 | }
39 |
40 | val settings = map[Keys.ARG_SETTINGS] as Map<*, *>
41 |
42 | sharedPreferences.edit()
43 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME,
44 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME] as String)
45 | .apply()
46 |
47 | sharedPreferences.edit()
48 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE,
49 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE] as String)
50 | .apply()
51 |
52 | sharedPreferences.edit()
53 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG,
54 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_MSG] as String)
55 | .apply()
56 |
57 | sharedPreferences.edit()
58 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG,
59 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG] as String)
60 | .apply()
61 |
62 | sharedPreferences.edit()
63 | .putString(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON,
64 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON] as String)
65 | .apply()
66 |
67 | sharedPreferences.edit()
68 | .putLong(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR,
69 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR] as Long)
70 | .apply()
71 |
72 | sharedPreferences.edit()
73 | .putInt(Keys.SETTINGS_INTERVAL,
74 | settings[Keys.SETTINGS_INTERVAL] as Int)
75 | .apply()
76 |
77 | sharedPreferences.edit()
78 | .putInt(Keys.SETTINGS_ACCURACY,
79 | settings[Keys.SETTINGS_ACCURACY] as Int)
80 | .apply()
81 |
82 | sharedPreferences.edit()
83 | .putFloat(Keys.SETTINGS_DISTANCE_FILTER,
84 | (settings[Keys.SETTINGS_DISTANCE_FILTER] as Double).toFloat())
85 | .apply()
86 |
87 | if (settings.containsKey(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME)) {
88 | sharedPreferences.edit()
89 | .putInt(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME,
90 | settings[Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME] as Int)
91 | .apply()
92 | }
93 |
94 | sharedPreferences.edit()
95 | .putInt(Keys.SETTINGS_ANDROID_LOCATION_CLIENT,
96 | settings[Keys.SETTINGS_ANDROID_LOCATION_CLIENT] as Int)
97 | .apply()
98 | }
99 |
100 | @JvmStatic
101 | fun getSettings(context: Context): Map {
102 | val sharedPreferences =
103 | context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
104 |
105 | val result = HashMap()
106 |
107 | result[Keys.ARG_CALLBACK_DISPATCHER] = sharedPreferences.getLong(Keys.ARG_CALLBACK_DISPATCHER, 0)
108 | result[Keys.ARG_CALLBACK] = sharedPreferences.getLong(Keys.ARG_CALLBACK, 0)
109 |
110 | if (sharedPreferences.contains(Keys.ARG_NOTIFICATION_CALLBACK)) {
111 | result[Keys.ARG_NOTIFICATION_CALLBACK] =
112 | sharedPreferences.getLong(Keys.ARG_NOTIFICATION_CALLBACK, 0)
113 | }
114 |
115 | val settings = HashMap()
116 |
117 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME] =
118 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_CHANNEL_NAME, "")
119 |
120 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE] =
121 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_TITLE, "")
122 |
123 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_MSG] =
124 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_MSG, "")
125 |
126 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG] =
127 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_BIG_MSG, "")
128 |
129 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON] =
130 | sharedPreferences.getString(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON, "")
131 |
132 | settings[Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR] =
133 | sharedPreferences.getLong(Keys.SETTINGS_ANDROID_NOTIFICATION_ICON_COLOR, 0)
134 |
135 | settings[Keys.SETTINGS_INTERVAL] =
136 | sharedPreferences.getInt(Keys.SETTINGS_INTERVAL, 0)
137 |
138 | settings[Keys.SETTINGS_ACCURACY] =
139 | sharedPreferences.getInt(Keys.SETTINGS_ACCURACY, 0)
140 |
141 | settings[Keys.SETTINGS_DISTANCE_FILTER] =
142 | sharedPreferences.getFloat(Keys.SETTINGS_DISTANCE_FILTER, 0f).toDouble()
143 |
144 | if (sharedPreferences.contains(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME)) {
145 | settings[Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME] = sharedPreferences.getInt(Keys.SETTINGS_ANDROID_WAKE_LOCK_TIME, 0)
146 | }
147 |
148 | settings[Keys.SETTINGS_ANDROID_LOCATION_CLIENT] =
149 | sharedPreferences.getInt(Keys.SETTINGS_ANDROID_LOCATION_CLIENT, 0)
150 |
151 | result[Keys.ARG_SETTINGS] = settings
152 | return result
153 | }
154 |
155 | @JvmStatic
156 | fun getLocationClient(context: Context): LocationClient {
157 | val sharedPreferences =
158 | context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
159 | val client = sharedPreferences.getInt(Keys.SETTINGS_ANDROID_LOCATION_CLIENT, 0)
160 | return LocationClient.fromInt(client) ?: LocationClient.Google
161 | }
162 |
163 | @JvmStatic
164 | fun setCallbackHandle(context: Context, key: String, handle: Long?) {
165 | if (handle == null) {
166 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
167 | .edit()
168 | .remove(key)
169 | .apply()
170 | return
171 | }
172 |
173 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
174 | .edit()
175 | .putLong(key, handle)
176 | .apply()
177 | }
178 |
179 | @JvmStatic
180 | fun setDataCallback(context: Context, key: String, data: Map<*, *>?) {
181 | if (data == null) {
182 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
183 | .edit()
184 | .remove(key)
185 | .apply()
186 | return
187 | }
188 | val dataStr = Gson().toJson(data)
189 | context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
190 | .edit()
191 | .putString(key, dataStr)
192 | .apply()
193 | }
194 |
195 | @JvmStatic
196 | fun getCallbackHandle(context: Context, key: String): Long? {
197 | val sharedPreferences = context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
198 | if (sharedPreferences.contains(key)) return sharedPreferences.getLong(key, 0L)
199 | return null
200 | }
201 |
202 | @JvmStatic
203 | fun getDataCallback(context: Context, key: String): Map<*, *> {
204 | val initialDataStr = context.getSharedPreferences(Keys.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
205 | .getString(key, null)
206 | val type = object : TypeToken