diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7ecebb4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.DS_Store
+.dart_tool/
+
+.packages
+.pub/
+pubspec.lock
+
+build/
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..41cc7d8
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.0.1
+
+* TODO: Describe initial release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ba75c69
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+TODO: Add your license here.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a22dc99
--- /dev/null
+++ b/README.md
@@ -0,0 +1,107 @@
+# Geofencing
+
+A sample geofencing plugin with background execution support for Flutter.
+
+## Getting Started
+This plugin works on both Android and iOS. Follow the instructions in the following sections for the
+platforms which are to be targeted.
+
+### Android
+
+Add the following lines to your `AndroidManifest.xml` to register the background service for
+geofencing:
+
+```xml
+
+
+```
+
+Also request the correct permissions for geofencing:
+
+```xml
+
+```
+
+Finally, create either `Application.kt` or `Application.java` in the same directory as `MainActivity`.
+
+For `Application.kt`, use the following:
+
+```kotlin
+class Application : FlutterApplication(), PluginRegistrantCallback {
+ override fun onCreate() {
+ super.onCreate();
+ GeofencingService.setPluginRegistrant(this);
+ }
+
+ override fun registerWith(registry: PluginRegistry) {
+ GeneratedPluginRegistrant.registerWith(registry);
+ }
+}
+```
+
+For `Application.java`, use the following:
+
+```java
+public class Application extends FlutterApplication implements PluginRegistrantCallback {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ GeofencingService.setPluginRegistrant(this);
+ }
+
+ @Override
+ public void registerWith(PluginRegistry registry) {
+ GeneratedPluginRegistrant.registerWith(registry);
+ }
+}
+```
+
+Which must also be referenced in `AndroidManifest.xml`:
+
+```xml
+
+ NSLocationAlwaysAndWhenInUseUsageDescription
+ YOUR DESCRIPTION HERE
+ NSLocationWhenInUseUsageDescription
+ YOUR DESCRIPTION HERE
+ ...
+```
+
+And request the correct permissions for geofencing:
+
+```xml
+
+ ...
+ Main
+ UIRequiredDeviceCapabilities
+
+ location-services
+ gps
+ armv7
+
+ UIBackgroundModes
+
+ location
+
+ ...
+
+```
+
+### Need Help?
+
+For help getting started with Flutter, view our online
+[documentation](https://flutter.io/).
+
+For help on editing plugin code, view the [documentation](https://flutter.io/developing-packages/#edit-plugin-package).
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000..c6cbe56
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1,8 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000..db71458
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,45 @@
+group 'io.flutter.plugins.geofencing'
+version '1.0-SNAPSHOT'
+
+buildscript {
+ ext.kotlin_version = '1.2.30'
+ repositories {
+ google()
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.1.2'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+rootProject.allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion 27
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+ defaultConfig {
+ minSdkVersion 16
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ lintOptions {
+ disable 'InvalidPackage'
+ }
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
+ implementation "com.google.android.gms:play-services-location:15.0.1"
+}
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 0000000..8bd86f6
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx1536M
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 0000000..63a0668
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'geofencing'
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1450f1a
--- /dev/null
+++ b/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/android/src/main/kotlin/io/flutter/plugins/geofencing/GeofencingBroadcastReceiver.kt b/android/src/main/kotlin/io/flutter/plugins/geofencing/GeofencingBroadcastReceiver.kt
new file mode 100644
index 0000000..a937b49
--- /dev/null
+++ b/android/src/main/kotlin/io/flutter/plugins/geofencing/GeofencingBroadcastReceiver.kt
@@ -0,0 +1,22 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.geofencing
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import io.flutter.view.FlutterMain
+
+
+class GeofencingBroadcastReceiver : BroadcastReceiver() {
+ companion object {
+ private const val TAG = "GeofencingBroadcastReceiver"
+ }
+ override fun onReceive(context: Context, intent: Intent) {
+ FlutterMain.ensureInitializationComplete(context, null)
+ GeofencingService.enqueueWork(context, intent)
+ }
+}
\ No newline at end of file
diff --git a/android/src/main/kotlin/io/flutter/plugins/geofencing/GeofencingPlugin.kt b/android/src/main/kotlin/io/flutter/plugins/geofencing/GeofencingPlugin.kt
new file mode 100644
index 0000000..3cc0aea
--- /dev/null
+++ b/android/src/main/kotlin/io/flutter/plugins/geofencing/GeofencingPlugin.kt
@@ -0,0 +1,137 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.geofencing
+
+import android.Manifest
+import android.app.Activity
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.util.Log
+import com.google.android.gms.location.Geofence
+import com.google.android.gms.location.GeofencingRequest
+import com.google.android.gms.location.LocationServices
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler
+import io.flutter.plugin.common.MethodChannel.Result
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.PluginRegistry.Registrar
+
+class GeofencingPlugin(context: Context, activity: Activity?) : MethodCallHandler {
+ private val mContext = context
+ private val mActivity = activity
+ private val mGeofencingClient = LocationServices.getGeofencingClient(mContext)
+
+ companion object {
+ @JvmStatic
+ private val TAG = "GeofencingPlugin"
+ @JvmStatic
+ val SHARED_PREFERENCES_KEY = "geofencing_plugin_cache"
+ @JvmStatic
+ val CALLBACK_HANDLE_KEY = "callback_handle"
+ @JvmStatic
+ val CALLBACK_DISPATCHER_HANDLE_KEY = "callback_dispatch_handler"
+ @JvmStatic
+ private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
+
+ @JvmStatic
+ fun registerWith(registrar: Registrar) {
+ val plugin = GeofencingPlugin(registrar.context(), registrar.activity())
+ val channel = MethodChannel(registrar.messenger(), "plugins.flutter.io/geofencing_plugin")
+ channel.setMethodCallHandler(plugin)
+ }
+ }
+
+ override fun onMethodCall(call: MethodCall, result: Result) {
+ val args = call.arguments() as? ArrayList<*>
+ when(call.method) {
+ "GeofencingPlugin.initializeService" -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ mActivity?.requestPermissions(REQUIRED_PERMISSIONS, 12312)
+ }
+ initializeService(args)
+ result.success(true)
+ }
+ "GeofencingPlugin.registerGeofence" -> registerGeofence(args, result)
+ "GeofencingPlugin.removeGeofence" -> removeGeofence(args, result)
+ else -> result.notImplemented()
+ }
+ }
+
+ private fun initializeService(args: ArrayList<*>?) {
+ Log.d(TAG, "Initializing GeofencingService")
+ val callbackHandle = args!![0] as Long
+ mContext.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
+ .edit()
+ .putLong(CALLBACK_DISPATCHER_HANDLE_KEY, callbackHandle)
+ .apply()
+ }
+
+ private fun getGeofencingRequest(geofence: Geofence, initialTrigger: Int): GeofencingRequest {
+ return GeofencingRequest.Builder().apply {
+ setInitialTrigger(initialTrigger)
+ addGeofence(geofence)
+ }.build()
+ }
+
+ private fun getGeofencePendingIndent(callbackHandle: Long): PendingIntent {
+ val intent = Intent(mContext, GeofencingBroadcastReceiver::class.java)
+ .putExtra(CALLBACK_HANDLE_KEY, callbackHandle)
+ return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ }
+
+ private fun registerGeofence(args: ArrayList<*>?, result: Result) {
+ val callbackHandle = args!![0] as Long
+ val id = args[1] as String
+ val lat = args[2] as Double
+ val long = args[3] as Double
+ val radius = (args[4] as Double).toFloat()
+ val fenceTriggers = args[5] as Int
+ val initialTriggers = args[6] as Int
+ val expirationDuration = (args[7] as Int).toLong()
+ val loiteringDelay = args[8] as Int
+ val notificationResponsiveness = args[9] as Int
+ val geofence = Geofence.Builder()
+ .setRequestId(id)
+ .setCircularRegion(lat, long, radius)
+ .setTransitionTypes(initialTriggers)
+ .setLoiteringDelay(loiteringDelay)
+ .setNotificationResponsiveness(notificationResponsiveness)
+ .setExpirationDuration(expirationDuration)
+ .build()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
+ (mContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
+ == PackageManager.PERMISSION_DENIED)) {
+ val msg = "'registerGeofence' requires the ACCESS_FINE_LOCATION permission."
+ Log.w(TAG, msg)
+ result.error(msg, null, null)
+ }
+ mGeofencingClient.addGeofences(getGeofencingRequest(geofence, fenceTriggers),
+ getGeofencePendingIndent(callbackHandle))?.run {
+ addOnSuccessListener {
+ Log.i(TAG, "Successfully added geofence")
+ result.success(true)
+ }
+ addOnFailureListener {
+ Log.e(TAG, "Failed to add geofence: $it")
+ result.error(it.toString(), null, null)
+ }
+ }
+ }
+
+ private fun removeGeofence(args: ArrayList<*>?, result: Result) {
+ val ids = listOf(args!![0] as String)
+ mGeofencingClient.removeGeofences(ids).run {
+ addOnSuccessListener {
+ result.success(true)
+ }
+ addOnFailureListener {
+ result.error(it.toString(), null, null)
+ }
+ }
+ }
+}
diff --git a/android/src/main/kotlin/io/flutter/plugins/geofencing/GeofencingService.kt b/android/src/main/kotlin/io/flutter/plugins/geofencing/GeofencingService.kt
new file mode 100644
index 0000000..67066aa
--- /dev/null
+++ b/android/src/main/kotlin/io/flutter/plugins/geofencing/GeofencingService.kt
@@ -0,0 +1,153 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.geofencing
+
+import android.content.Context
+import android.content.Intent
+import android.os.IBinder
+import android.os.PowerManager
+import android.support.v4.app.JobIntentService
+import android.util.Log
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler
+import io.flutter.plugin.common.MethodChannel.Result
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
+import io.flutter.view.FlutterCallbackInformation
+import io.flutter.view.FlutterMain
+import io.flutter.view.FlutterNativeView
+import io.flutter.view.FlutterRunArguments
+import java.util.ArrayDeque
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.UUID
+
+import com.google.android.gms.location.GeofencingEvent
+
+class GeofencingService : MethodCallHandler, JobIntentService() {
+ private val queue = ArrayDeque>()
+ private lateinit var mBackgroundChannel: MethodChannel
+ private lateinit var mContext: Context
+
+ companion object {
+ @JvmStatic
+ private val TAG = "GeofencingService"
+ @JvmStatic
+ private val JOB_ID = UUID.randomUUID().mostSignificantBits.toInt()
+ @JvmStatic
+ private var sBackgroundFlutterView: FlutterNativeView? = null
+ @JvmStatic
+ private val sServiceStarted = AtomicBoolean(false)
+
+ @JvmStatic
+ private lateinit var sPluginRegistrantCallback: PluginRegistrantCallback
+
+ @JvmStatic
+ fun enqueueWork(context: Context, work: Intent) {
+ enqueueWork(context, GeofencingService::class.java, JOB_ID, work)
+ }
+
+ @JvmStatic
+ fun setPluginRegistrant(callback: PluginRegistrantCallback) {
+ sPluginRegistrantCallback = callback
+ }
+ }
+
+ private fun startGeofencingService(context: Context) {
+ synchronized(sServiceStarted) {
+ mContext = context
+ if (sBackgroundFlutterView == null) {
+ val callbackHandle = context.getSharedPreferences(
+ GeofencingPlugin.SHARED_PREFERENCES_KEY,
+ Context.MODE_PRIVATE)
+ .getLong(GeofencingPlugin.CALLBACK_DISPATCHER_HANDLE_KEY, 0)
+
+ val callbackInfo = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle)
+ if (callbackInfo == null) {
+ Log.e(TAG, "Fatal: failed to find callback")
+ return
+ }
+ Log.i(TAG, "Starting GeofencingService...")
+ sBackgroundFlutterView = FlutterNativeView(context, true)
+
+ val registry = sBackgroundFlutterView!!.pluginRegistry
+ sPluginRegistrantCallback.registerWith(registry)
+ val args = FlutterRunArguments()
+ args.bundlePath = FlutterMain.findAppBundlePath(context)
+ args.entrypoint = callbackInfo.callbackName
+ args.libraryPath = callbackInfo.callbackLibraryPath
+
+ sBackgroundFlutterView!!.runFromBundle(args)
+ IsolateHolderService.setBackgroundFlutterView(sBackgroundFlutterView)
+ }
+ }
+ mBackgroundChannel = MethodChannel(sBackgroundFlutterView,
+ "plugins.flutter.io/geofencing_plugin_background")
+ mBackgroundChannel.setMethodCallHandler(this)
+ }
+
+ override fun onMethodCall(call: MethodCall, result: Result) {
+ when(call.method) {
+ "GeofencingService.initialized" -> {
+ synchronized(sServiceStarted) {
+ while (!queue.isEmpty()) {
+ mBackgroundChannel.invokeMethod("", queue.remove())
+ }
+ sServiceStarted.set(true)
+ }
+ }
+ "GeofencingService.promoteToForeground" -> {
+ mContext.startForegroundService(Intent(mContext, IsolateHolderService::class.java))
+ }
+ "GeofencingService.demoteToBackground" -> {
+ val intent = Intent(mContext, IsolateHolderService::class.java)
+ intent.setAction(IsolateHolderService.ACTION_SHUTDOWN)
+ mContext.startForegroundService(intent)
+ }
+ else -> result.notImplemented()
+ }
+ result.success(null)
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ startGeofencingService(this)
+ }
+
+ override fun onHandleWork(intent: Intent) {
+ val callbackHandle = intent.getLongExtra(GeofencingPlugin.CALLBACK_HANDLE_KEY, 0)
+ val geofencingEvent = GeofencingEvent.fromIntent(intent)
+ if (geofencingEvent.hasError()) {
+ Log.e(TAG, "Geofencing error: ${geofencingEvent.errorCode}")
+ return
+ }
+
+ // Get the transition type.
+ val geofenceTransition = geofencingEvent.geofenceTransition
+
+ // Get the geofences that were triggered. A single event can trigger
+ // multiple geofences.
+ val triggeringGeofences = geofencingEvent.triggeringGeofences.map {
+ it.requestId
+ }
+
+ val location = geofencingEvent.triggeringLocation
+ val locationList = listOf(location.latitude,
+ location.longitude)
+ val geofenceUpdateList = listOf(callbackHandle,
+ triggeringGeofences,
+ locationList,
+ geofenceTransition)
+
+ synchronized(sServiceStarted) {
+ if (!sServiceStarted.get()) {
+ // Queue up geofencing events while background isolate is starting
+ queue.add(geofenceUpdateList)
+ } else {
+ // Callback method name is intentionally left blank.
+ mBackgroundChannel.invokeMethod("", geofenceUpdateList)
+ }
+ }
+ }
+}
diff --git a/android/src/main/kotlin/io/flutter/plugins/geofencing/IsolateHolderService.kt b/android/src/main/kotlin/io/flutter/plugins/geofencing/IsolateHolderService.kt
new file mode 100644
index 0000000..a222bfe
--- /dev/null
+++ b/android/src/main/kotlin/io/flutter/plugins/geofencing/IsolateHolderService.kt
@@ -0,0 +1,78 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.geofencing
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.os.IBinder
+import android.os.PowerManager
+import android.support.v4.app.NotificationCompat
+import android.util.Log
+import io.flutter.view.FlutterNativeView
+
+class IsolateHolderService : Service() {
+ companion object {
+ @JvmStatic
+ val ACTION_SHUTDOWN = "SHUTDOWN"
+ @JvmStatic
+ private val WAKELOCK_TAG = "IsolateHolderService::WAKE_LOCK"
+ @JvmStatic
+ private val TAG = "IsolateHolderService"
+ @JvmStatic
+ private var sBackgroundFlutterView: FlutterNativeView? = null
+
+ @JvmStatic
+ fun setBackgroundFlutterView(view: FlutterNativeView?) {
+ sBackgroundFlutterView = view
+ }
+ }
+
+ override fun onBind(p0: Intent) : IBinder? {
+ return null;
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ val CHANNEL_ID = "geofencing_plugin_channel"
+ val channel = NotificationChannel(CHANNEL_ID,
+ "Flutter Geofencing Plugin",
+ NotificationManager.IMPORTANCE_LOW)
+ val imageId = getResources().getIdentifier("ic_launcher", "mipmap", getPackageName())
+
+ (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel)
+ val notification = NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle("Almost home!")
+ .setContentText("Within 1KM of home. Fine location tracking enabled.")
+ .setSmallIcon(imageId)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .build()
+
+ (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
+ newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG).apply {
+ setReferenceCounted(false)
+ acquire()
+ }
+ }
+ startForeground(1, notification)
+ }
+
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int) : Int {
+ if (intent.getAction() == ACTION_SHUTDOWN) {
+ (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
+ newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG).apply {
+ if (isHeld()) {
+ release()
+ }
+ }
+ }
+ stopForeground(true)
+ stopSelf()
+ }
+ return START_STICKY;
+ }
+}
diff --git a/example/.gitignore b/example/.gitignore
new file mode 100644
index 0000000..dee655c
--- /dev/null
+++ b/example/.gitignore
@@ -0,0 +1,9 @@
+.DS_Store
+.dart_tool/
+
+.packages
+.pub/
+
+build/
+
+.flutter-plugins
diff --git a/example/.idea/libraries/Dart_SDK.xml b/example/.idea/libraries/Dart_SDK.xml
new file mode 100644
index 0000000..b196fc3
--- /dev/null
+++ b/example/.idea/libraries/Dart_SDK.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/.idea/libraries/Flutter_for_Android.xml b/example/.idea/libraries/Flutter_for_Android.xml
new file mode 100644
index 0000000..1d58969
--- /dev/null
+++ b/example/.idea/libraries/Flutter_for_Android.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/example/.idea/modules.xml b/example/.idea/modules.xml
new file mode 100644
index 0000000..3e2f984
--- /dev/null
+++ b/example/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/example/.idea/runConfigurations/main_dart.xml b/example/.idea/runConfigurations/main_dart.xml
new file mode 100644
index 0000000..aab7b5c
--- /dev/null
+++ b/example/.idea/runConfigurations/main_dart.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/.idea/workspace.xml b/example/.idea/workspace.xml
new file mode 100644
index 0000000..5b3388c
--- /dev/null
+++ b/example/.idea/workspace.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/.metadata b/example/.metadata
new file mode 100644
index 0000000..94aa150
--- /dev/null
+++ b/example/.metadata
@@ -0,0 +1,8 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: 7762e451159823e2c605c8f444f7b4f5b2f3784f
+ channel: master
diff --git a/example/README.md b/example/README.md
new file mode 100644
index 0000000..097a88d
--- /dev/null
+++ b/example/README.md
@@ -0,0 +1,8 @@
+# geofencing_example
+
+Demonstrates how to use the geofencing plugin.
+
+## Getting Started
+
+For help getting started with Flutter, view our online
+[documentation](https://flutter.io/).
diff --git a/example/android/.gitignore b/example/android/.gitignore
new file mode 100644
index 0000000..65b7315
--- /dev/null
+++ b/example/android/.gitignore
@@ -0,0 +1,10 @@
+*.iml
+*.class
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+GeneratedPluginRegistrant.java
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
new file mode 100644
index 0000000..33fe8f1
--- /dev/null
+++ b/example/android/app/build.gradle
@@ -0,0 +1,67 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+ localPropertiesFile.withReader('UTF-8') { reader ->
+ localProperties.load(reader)
+ }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+ throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+ flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+ flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+ compileSdkVersion 27
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+ lintOptions {
+ disable 'InvalidPackage'
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId "io.flutter.plugins.geofencingexample"
+ minSdkVersion 16
+ targetSdkVersion 27
+ versionCode flutterVersionCode.toInteger()
+ versionName flutterVersionName
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig signingConfigs.debug
+ }
+ }
+}
+
+flutter {
+ source '../..'
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+}
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9d97858
--- /dev/null
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/android/app/src/main/kotlin/io/flutter/plugins/geofencingexample/Application.kt b/example/android/app/src/main/kotlin/io/flutter/plugins/geofencingexample/Application.kt
new file mode 100644
index 0000000..19faf50
--- /dev/null
+++ b/example/android/app/src/main/kotlin/io/flutter/plugins/geofencingexample/Application.kt
@@ -0,0 +1,18 @@
+package io.flutter.plugins.geofencingexample
+
+import io.flutter.app.FlutterApplication
+import io.flutter.plugin.common.PluginRegistry
+import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
+import io.flutter.plugins.GeneratedPluginRegistrant
+import io.flutter.plugins.geofencing.GeofencingService
+
+class Application : FlutterApplication(), PluginRegistrantCallback {
+ override fun onCreate() {
+ super.onCreate();
+ GeofencingService.setPluginRegistrant(this);
+ }
+
+ override fun registerWith(registry: PluginRegistry) {
+ GeneratedPluginRegistrant.registerWith(registry);
+ }
+}
\ No newline at end of file
diff --git a/example/android/app/src/main/kotlin/io/flutter/plugins/geofencingexample/MainActivity.kt b/example/android/app/src/main/kotlin/io/flutter/plugins/geofencingexample/MainActivity.kt
new file mode 100644
index 0000000..f490682
--- /dev/null
+++ b/example/android/app/src/main/kotlin/io/flutter/plugins/geofencingexample/MainActivity.kt
@@ -0,0 +1,13 @@
+package io.flutter.plugins.geofencingexample
+
+import android.os.Bundle
+
+import io.flutter.app.FlutterActivity
+import io.flutter.plugins.GeneratedPluginRegistrant
+
+class MainActivity(): FlutterActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ GeneratedPluginRegistrant.registerWith(this)
+ }
+}
diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/example/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
Binary files /dev/null and b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
Binary files /dev/null and b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..00fa441
--- /dev/null
+++ b/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/example/android/build.gradle b/example/android/build.gradle
new file mode 100644
index 0000000..830798d
--- /dev/null
+++ b/example/android/build.gradle
@@ -0,0 +1,31 @@
+buildscript {
+ ext.kotlin_version = '1.2.30'
+ repositories {
+ google()
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.1.2'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/example/android/gradle.properties b/example/android/gradle.properties
new file mode 100644
index 0000000..8bd86f6
--- /dev/null
+++ b/example/android/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx1536M
diff --git a/example/android/gradle/wrapper/gradle-wrapper.jar b/example/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
Binary files /dev/null and b/example/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..9372d0f
--- /dev/null
+++ b/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/example/android/gradlew b/example/android/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/example/android/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/example/android/gradlew.bat b/example/android/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/example/android/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/example/android/settings.gradle b/example/android/settings.gradle
new file mode 100644
index 0000000..5a2f14f
--- /dev/null
+++ b/example/android/settings.gradle
@@ -0,0 +1,15 @@
+include ':app'
+
+def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
+
+def plugins = new Properties()
+def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
+if (pluginsFile.exists()) {
+ pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
+}
+
+plugins.each { name, path ->
+ def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
+ include ":$name"
+ project(":$name").projectDir = pluginDirectory
+}
diff --git a/example/geofencing_example.iml b/example/geofencing_example.iml
new file mode 100644
index 0000000..e5c8371
--- /dev/null
+++ b/example/geofencing_example.iml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/geofencing_example_android.iml b/example/geofencing_example_android.iml
new file mode 100644
index 0000000..b050030
--- /dev/null
+++ b/example/geofencing_example_android.iml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/ios/.gitignore b/example/ios/.gitignore
new file mode 100644
index 0000000..79cc4da
--- /dev/null
+++ b/example/ios/.gitignore
@@ -0,0 +1,45 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+/Flutter/app.flx
+/Flutter/app.zip
+/Flutter/flutter_assets/
+/Flutter/App.framework
+/Flutter/Flutter.framework
+/Flutter/Generated.xcconfig
+/ServiceDefinitions.json
+
+Pods/
+.symlinks/
diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..9367d48
--- /dev/null
+++ b/example/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 8.0
+
+
diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000..e8efba1
--- /dev/null
+++ b/example/ios/Flutter/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "Generated.xcconfig"
diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000..399e934
--- /dev/null
+++ b/example/ios/Flutter/Release.xcconfig
@@ -0,0 +1,2 @@
+#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "Generated.xcconfig"
diff --git a/example/ios/Podfile b/example/ios/Podfile
new file mode 100644
index 0000000..7c6cb6f
--- /dev/null
+++ b/example/ios/Podfile
@@ -0,0 +1,63 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '9.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+def parse_KV_file(file, separator='=')
+ file_abs_path = File.expand_path(file)
+ if !File.exists? file_abs_path
+ return [];
+ end
+ pods_ary = []
+ skip_line_start_symbols = ["#", "/"]
+ File.foreach(file_abs_path) { |line|
+ next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
+ plugin = line.split(pattern=separator)
+ if plugin.length == 2
+ podname = plugin[0].strip()
+ path = plugin[1].strip()
+ podpath = File.expand_path("#{path}", file_abs_path)
+ pods_ary.push({:name => podname, :path => podpath});
+ else
+ puts "Invalid plugin specification: #{line}"
+ end
+ }
+ return pods_ary
+end
+
+target 'Runner' do
+ # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
+ # referring to absolute paths on developers' machines.
+ system('rm -rf .symlinks')
+ system('mkdir -p .symlinks/plugins')
+
+ # Flutter Pods
+ generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
+ if generated_xcode_build_settings.empty?
+ puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
+ end
+ generated_xcode_build_settings.map { |p|
+ if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
+ symlink = File.join('.symlinks', 'flutter')
+ File.symlink(File.dirname(p[:path]), symlink)
+ pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
+ end
+ }
+
+ # Plugin Pods
+ plugin_pods = parse_KV_file('../.flutter-plugins')
+ plugin_pods.map { |p|
+ symlink = File.join('.symlinks', 'plugins', p[:name])
+ File.symlink(p[:path], symlink)
+ pod p[:name], :path => File.join(symlink, 'ios')
+ }
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ target.build_configurations.each do |config|
+ config.build_settings['ENABLE_BITCODE'] = 'NO'
+ end
+ end
+end
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
new file mode 100644
index 0000000..b205298
--- /dev/null
+++ b/example/ios/Podfile.lock
@@ -0,0 +1,22 @@
+PODS:
+ - Flutter (1.0.0)
+ - geofencing (0.0.1):
+ - Flutter
+
+DEPENDENCIES:
+ - Flutter (from `.symlinks/flutter/ios`)
+ - geofencing (from `.symlinks/plugins/geofencing/ios`)
+
+EXTERNAL SOURCES:
+ Flutter:
+ :path: ".symlinks/flutter/ios"
+ geofencing:
+ :path: ".symlinks/plugins/geofencing/ios"
+
+SPEC CHECKSUMS:
+ Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296
+ geofencing: 03e5e092597a2619fb90fc277d31932752595b00
+
+PODFILE CHECKSUM: 1e5af4103afd21ca5ead147d7b81d06f494f51a2
+
+COCOAPODS: 1.5.0
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..4bfefc7
--- /dev/null
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,496 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; };
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
+ 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
+ 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 973C541AD22AEBFA99FE6326 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 93ABD7C612988E5FEDAEC874 /* libPods-Runner.a */; };
+ 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
+ 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
+ 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
+ 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
+ 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
+ 93ABD7C612988E5FEDAEC874 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
+ 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
+ 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
+ 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 97C146EB1CF9000F007C117D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
+ 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
+ 973C541AD22AEBFA99FE6326 /* libPods-Runner.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 6CBC30358DFF51EA0A13FCBB /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Pods;
+ sourceTree = "";
+ };
+ 9740EEB11CF90186004384FC /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 2D5378251FAA1A9400D5DBA9 /* flutter_assets */,
+ 3B80C3931E831B6300D905FE /* App.framework */,
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+ 9740EEBA1CF902C7004384FC /* Flutter.framework */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */,
+ );
+ name = Flutter;
+ sourceTree = "";
+ };
+ 97C146E51CF9000F007C117D = {
+ isa = PBXGroup;
+ children = (
+ 9740EEB11CF90186004384FC /* Flutter */,
+ 97C146F01CF9000F007C117D /* Runner */,
+ 97C146EF1CF9000F007C117D /* Products */,
+ 6CBC30358DFF51EA0A13FCBB /* Pods */,
+ BC45E5689E17726DF2ACA24F /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 97C146EF1CF9000F007C117D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146EE1CF9000F007C117D /* Runner.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 97C146F01CF9000F007C117D /* Runner */ = {
+ isa = PBXGroup;
+ children = (
+ 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
+ 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
+ 97C146FA1CF9000F007C117D /* Main.storyboard */,
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */,
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+ 97C147021CF9000F007C117D /* Info.plist */,
+ 97C146F11CF9000F007C117D /* Supporting Files */,
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+ );
+ path = Runner;
+ sourceTree = "";
+ };
+ 97C146F11CF9000F007C117D /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146F21CF9000F007C117D /* main.m */,
+ );
+ name = "Supporting Files";
+ sourceTree = "";
+ };
+ BC45E5689E17726DF2ACA24F /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 93ABD7C612988E5FEDAEC874 /* libPods-Runner.a */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 97C146ED1CF9000F007C117D /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ A382B369948A6D85048CDC2F /* [CP] Check Pods Manifest.lock */,
+ 9740EEB61CF901F6004384FC /* Run Script */,
+ 97C146EA1CF9000F007C117D /* Sources */,
+ 97C146EB1CF9000F007C117D /* Frameworks */,
+ 97C146EC1CF9000F007C117D /* Resources */,
+ 9705A1C41CF9048500538489 /* Embed Frameworks */,
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ 96A6A0AE849E95E0E8EFCB70 /* [CP] Embed Pods Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Runner;
+ productName = Runner;
+ productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 97C146E61CF9000F007C117D /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0910;
+ ORGANIZATIONNAME = "The Chromium Authors";
+ TargetAttributes = {
+ 97C146ED1CF9000F007C117D = {
+ CreatedOnToolsVersion = 7.3.1;
+ DevelopmentTeam = U8PR3Q5466;
+ };
+ };
+ };
+ buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 97C146E51CF9000F007C117D;
+ productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 97C146ED1CF9000F007C117D /* Runner */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 97C146EC1CF9000F007C117D /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+ 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+ 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */,
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Thin Binary";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
+ };
+ 96A6A0AE849E95E0E8EFCB70 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
+ "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 9740EEB61CF901F6004384FC /* Run Script */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Run Script";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+ };
+ A382B369948A6D85048CDC2F /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 97C146EA1CF9000F007C117D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
+ 97C146F31CF9000F007C117D /* main.m in Sources */,
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C146FB1CF9000F007C117D /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C147001CF9000F007C117D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 97C147031CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 97C147041CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 97C147061CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = U8PR3Q5466;
+ ENABLE_BITCODE = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.geofencing;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Debug;
+ };
+ 97C147071CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = U8PR3Q5466;
+ ENABLE_BITCODE = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.geofencing;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147031CF9000F007C117D /* Debug */,
+ 97C147041CF9000F007C117D /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147061CF9000F007C117D /* Debug */,
+ 97C147071CF9000F007C117D /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..1263ac8
--- /dev/null
+++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..21a3cc1
--- /dev/null
+++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/example/ios/Runner/AppDelegate.h b/example/ios/Runner/AppDelegate.h
new file mode 100644
index 0000000..36e21bb
--- /dev/null
+++ b/example/ios/Runner/AppDelegate.h
@@ -0,0 +1,6 @@
+#import
+#import
+
+@interface AppDelegate : FlutterAppDelegate
+
+@end
diff --git a/example/ios/Runner/AppDelegate.m b/example/ios/Runner/AppDelegate.m
new file mode 100644
index 0000000..0580979
--- /dev/null
+++ b/example/ios/Runner/AppDelegate.m
@@ -0,0 +1,14 @@
+#include "AppDelegate.h"
+#include "GeneratedPluginRegistrant.h"
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ [GeneratedPluginRegistrant registerWithRegistry:self];
+
+ // Override point for customization after application launch.
+ return [super application:application didFinishLaunchingWithOptions:launchOptions];
+}
+
+@end
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d36b1fa
--- /dev/null
+++ b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+ "images" : [
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-83.5x83.5@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "Icon-App-1024x1024@1x.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 0000000..3d43d11
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 0000000..28c6bf0
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000..2ccbfd9
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 0000000..f091b6b
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000..4cde121
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000..d0ef06e
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000..dcdc230
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 0000000..2ccbfd9
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000..c8f9ed8
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 0000000..a6d6b86
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000..a6d6b86
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000..75b2d16
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000..c4df70d
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000..6a84f41
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000..d0e1f58
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+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.
\ No newline at end of file
diff --git a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/example/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
new file mode 100644
index 0000000..66743fc
--- /dev/null
+++ b/example/ios/Runner/Info.plist
@@ -0,0 +1,59 @@
+
+
+
+
+ NSLocationAlwaysAndWhenInUseUsageDescription
+ Required for Flutter Geofencing example events.
+ NSLocationWhenInUseUsageDescription
+ Required for Flutter Geofencing example events.
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ geofencing_example
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UIRequiredDeviceCapabilities
+
+ location-services
+ gps
+ armv7
+
+ UIBackgroundModes
+
+ location
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+
+
diff --git a/example/ios/Runner/main.m b/example/ios/Runner/main.m
new file mode 100644
index 0000000..dff6597
--- /dev/null
+++ b/example/ios/Runner/main.m
@@ -0,0 +1,9 @@
+#import
+#import
+#import "AppDelegate.h"
+
+int main(int argc, char* argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/example/lib/main.dart b/example/lib/main.dart
new file mode 100644
index 0000000..b3c2718
--- /dev/null
+++ b/example/lib/main.dart
@@ -0,0 +1,148 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:isolate';
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'package:geofencing/geofencing.dart';
+
+void main() => runApp(MyApp());
+
+class MyApp extends StatefulWidget {
+ @override
+ _MyAppState createState() => _MyAppState();
+}
+
+class _MyAppState extends State {
+ String geofenceState = 'N/A';
+ double latitude = 37.419851;
+ double longitude = -122.078818;
+ double radius = 150.0;
+ ReceivePort port = ReceivePort();
+ final List triggers = [
+ GeofenceEvent.enter,
+ GeofenceEvent.dwell,
+ GeofenceEvent.exit
+ ];
+ final AndroidGeofencingSettings androidSettings = AndroidGeofencingSettings(
+ initialTrigger: [
+ GeofenceEvent.enter,
+ GeofenceEvent.exit,
+ GeofenceEvent.dwell
+ ],
+ loiteringDelay: 1000 * 60);
+
+ @override
+ void initState() {
+ super.initState();
+ IsolateNameServer.registerPortWithName(
+ port.sendPort, 'geofencing_send_port');
+ port.listen((dynamic data) {
+ print('Event: $data');
+ setState(() {
+ geofenceState = data;
+ });
+ });
+ initPlatformState();
+ }
+
+ static void callback(List ids, Location l, GeofenceEvent e) async {
+ print('Fences: $ids Location $l Event: $e');
+ final SendPort send =
+ IsolateNameServer.lookupPortByName('geofencing_send_port');
+ send?.send(e.toString());
+ }
+
+ // Platform messages are asynchronous, so we initialize in an async method.
+ Future initPlatformState() async {
+ print('Initializing...');
+ await GeofencingManager.initialize();
+ print('Initialization done');
+ }
+
+ String numberValidator(String value) {
+ if (value == null) {
+ return null;
+ }
+ final num a = num.tryParse(value);
+ if (a == null) {
+ return '"$value" is not a valid number';
+ }
+ return null;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ home: Scaffold(
+ appBar: AppBar(
+ title: const Text('Flutter Geofencing Example'),
+ ),
+ body: Container(
+ padding: const EdgeInsets.all(20.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text('Current state: $geofenceState'),
+ Center(
+ child: RaisedButton(
+ child: const Text('Register'),
+ onPressed: () {
+ if (latitude == null) {
+ setState(() => latitude = 0.0);
+ }
+ if (longitude == null) {
+ setState(() => longitude = 0.0);
+ }
+ if (radius == null) {
+ setState(() => radius = 0.0);
+ }
+ GeofencingManager.registerGeofence(
+ GeofenceRegion(
+ 'mtv', latitude, longitude, radius, triggers,
+ androidSettings: androidSettings),
+ callback);
+ },
+ ),
+ ),
+ Center(
+ child: RaisedButton(
+ child: const Text('Unregister'),
+ onPressed: () =>
+ GeofencingManager.removeGeofenceById('mtv')),
+ ),
+ TextField(
+ decoration: const InputDecoration(
+ hintText: 'Latitude',
+ ),
+ keyboardType: TextInputType.number,
+ controller:
+ TextEditingController(text: latitude.toString()),
+ onChanged: (String s) {
+ latitude = double.tryParse(s);
+ },
+ ),
+ TextField(
+ decoration:
+ const InputDecoration(hintText: 'Longitude'),
+ keyboardType: TextInputType.number,
+ controller:
+ TextEditingController(text: longitude.toString()),
+ onChanged: (String s) {
+ longitude = double.tryParse(s);
+ }),
+ TextField(
+ decoration: const InputDecoration(hintText: 'Radius'),
+ keyboardType: TextInputType.number,
+ controller:
+ TextEditingController(text: radius.toString()),
+ onChanged: (String s) {
+ radius = double.tryParse(s);
+ }),
+ ]))),
+ );
+ }
+}
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
new file mode 100644
index 0000000..3588d5d
--- /dev/null
+++ b/example/pubspec.yaml
@@ -0,0 +1,67 @@
+name: geofencing_example
+description: Demonstrates how to use the geofencing plugin.
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# Read more about versioning at semver.org.
+version: 1.0.0+1
+
+dependencies:
+ flutter:
+ sdk: flutter
+
+ # The following adds the Cupertino Icons font to your application.
+ # Use with the CupertinoIcons class for iOS style icons.
+ cupertino_icons: ^0.1.2
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+ geofencing:
+ path: ../
+
+# For information on the generic Dart part of this file, see the
+# following page: https://www.dartlang.org/tools/pub/pubspec
+
+# The following section is specific to Flutter.
+flutter:
+
+ # The following line ensures that the Material Icons font is
+ # included with your application, so that you can use the icons in
+ # the material Icons class.
+ uses-material-design: true
+
+ # To add assets to your application, add an assets section, like this:
+ # assets:
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
+
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.io/assets-and-images/#resolution-aware.
+
+ # For details regarding adding assets from package dependencies, see
+ # https://flutter.io/assets-and-images/#from-packages
+
+ # To add custom fonts to your application, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts from package dependencies,
+ # see https://flutter.io/custom-fonts/#from-packages
diff --git a/geofencing.iml b/geofencing.iml
new file mode 100644
index 0000000..429df7d
--- /dev/null
+++ b/geofencing.iml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/geofencing_android.iml b/geofencing_android.iml
new file mode 100644
index 0000000..ac5d744
--- /dev/null
+++ b/geofencing_android.iml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/.gitignore b/ios/.gitignore
new file mode 100644
index 0000000..710ec6c
--- /dev/null
+++ b/ios/.gitignore
@@ -0,0 +1,36 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+/Flutter/Generated.xcconfig
diff --git a/ios/Assets/.gitkeep b/ios/Assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/ios/Classes/GeofencingPlugin.h b/ios/Classes/GeofencingPlugin.h
new file mode 100644
index 0000000..7943d4e
--- /dev/null
+++ b/ios/Classes/GeofencingPlugin.h
@@ -0,0 +1,11 @@
+#ifndef GeofencingPlugin_h
+#define GeofencingPlugin_h
+
+#import
+
+#import
+
+@interface GeofencingPlugin : NSObject
+
+@end
+#endif
\ No newline at end of file
diff --git a/ios/Classes/GeofencingPlugin.m b/ios/Classes/GeofencingPlugin.m
new file mode 100644
index 0000000..ae0fb52
--- /dev/null
+++ b/ios/Classes/GeofencingPlugin.m
@@ -0,0 +1,251 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file
+
+#import "GeofencingPlugin.h"
+
+#import
+
+@implementation GeofencingPlugin {
+ CLLocationManager *_locationManager;
+ FlutterHeadlessDartRunner *_headlessRunner;
+ FlutterMethodChannel *_callbackChannel;
+ FlutterMethodChannel *_mainChannel;
+ NSObject *_registrar;
+ NSUserDefaults *_persistentState;
+ NSMutableArray *_eventQueue;
+ int64_t _onLocationUpdateHandle;
+}
+
+static const NSString *kRegionKey = @"region";
+static const NSString *kEventType = @"event_type";
+static const int kEnterEvent = 1;
+static const int kExitEvent = 2;
+static const NSString *kCallbackMapping = @"geofence_region_callback_mapping";
+static GeofencingPlugin *instance = nil;
+static BOOL initialized = NO;
+
+#pragma mark FlutterPlugin Methods
+
++ (void)registerWithRegistrar:(NSObject *)registrar {
+ @synchronized(self) {
+ if (instance == nil) {
+ NSLog(@"Registering with registrar");
+ instance = [[GeofencingPlugin alloc] init:registrar];
+ [registrar addApplicationDelegate:instance];
+ }
+ }
+}
+
+- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
+ NSArray *arguments = call.arguments;
+ if ([@"GeofencingPlugin.initializeService" isEqualToString:call.method]) {
+ NSAssert(arguments.count == 1,
+ @"Invalid argument count for 'GeofencingPlugin.initializeService'");
+ [self startGeofencingService:[arguments[0] longValue]];
+ result(@(YES));
+ } else if ([@"GeofencingService.initialized" isEqualToString:call.method]) {
+ @synchronized(self) {
+ initialized = YES;
+ // Send the geofence events that occurred while the background
+ // isolate was initializing.
+ while ([_eventQueue count] > 0) {
+ NSLog(@"DUMPING QUEUE");
+ NSDictionary* event = _eventQueue[0];
+ [_eventQueue removeObjectAtIndex:0];
+ CLRegion* region = [event objectForKey:kRegionKey];
+ int type = [[event objectForKey:kEventType] intValue];
+ [self sendLocationEvent:region eventType: type];
+ }
+ }
+ result(nil);
+ } else if ([@"GeofencingPlugin.registerGeofence" isEqualToString:call.method]) {
+ [self registerGeofence:arguments];
+ result(@(YES));
+ } else if ([@"GeofencingPlugin.removeGeofence" isEqualToString:call.method]) {
+ result(@([self removeGeofence:arguments]));
+ } else {
+ result(FlutterMethodNotImplemented);
+ }
+}
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ // Check to see if we're being launched due to a location event.
+ if (launchOptions[UIApplicationLaunchOptionsLocationKey] != nil) {
+ // Restart the headless service.
+ [self startGeofencingService:[self getCallbackDispatcherHandle]];
+ }
+
+ // Note: if we return NO, this vetos the launch of the application.
+ return YES;
+}
+
+#pragma mark LocationManagerDelegate Methods
+- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
+ @synchronized(self) {
+ NSLog(@"Entered Location");
+ if (initialized) {
+ [self sendLocationEvent:region eventType:kEnterEvent];
+ } else {
+ NSDictionary *dict = @{
+ kRegionKey: region,
+ kEventType: @(kEnterEvent)
+ };
+ [_eventQueue addObject:dict];
+ }
+ }
+}
+
+- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
+ @synchronized(self) {
+ NSLog(@"Exited location");
+ if (initialized) {
+ [self sendLocationEvent:region eventType:kExitEvent];
+ } else {
+ NSDictionary *dict = @{
+ kRegionKey: region,
+ kEventType: @(kExitEvent)
+ };
+ [_eventQueue addObject:dict];
+ }
+ }
+}
+
+- (void)locationManager:(CLLocationManager *)manager
+ monitoringDidFailForRegion:(CLRegion *)region
+ withError:(NSError *)error {
+ NSLog(@"Monitoring error %@: %@", error.code, error.localizedDescription);
+}
+
+#pragma mark GeofencingPlugin Methods
+
+- (void)sendLocationEvent:(CLRegion *)region eventType:(int)event {
+ NSAssert([region isKindOfClass:[CLCircularRegion class]], @"region must be CLCircularRegion");
+ CLLocationCoordinate2D center = region.center;
+ int64_t handle = [self getCallbackHandleForRegionId:region.identifier];
+ [_callbackChannel
+ invokeMethod:@""
+ arguments:@[
+ @(handle), @[ region.identifier ], @[ @(center.latitude), @(center.longitude) ], @(event)
+ ]];
+}
+
+- (instancetype)init:(NSObject *)registrar {
+ self = [super init];
+ NSAssert(self, @"super init cannot be nil");
+ _persistentState = [NSUserDefaults standardUserDefaults];
+ _eventQueue = [[NSMutableArray alloc] init];
+ _locationManager = [[CLLocationManager alloc] init];
+ [_locationManager setDelegate:self];
+ [_locationManager requestAlwaysAuthorization];
+ _locationManager.allowsBackgroundLocationUpdates = YES;
+
+ _headlessRunner = [[FlutterHeadlessDartRunner alloc] init];
+ _registrar = registrar;
+
+ _mainChannel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/geofencing_plugin"
+ binaryMessenger:[registrar messenger]];
+ [registrar addMethodCallDelegate:self channel:_mainChannel];
+
+ _callbackChannel =
+ [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/geofencing_plugin_background"
+ binaryMessenger:_headlessRunner];
+ return self;
+}
+
+- (void)startGeofencingService:(int64_t)handle {
+ NSLog(@"Initializing GeofencingService");
+ [self setCallbackDispatcherHandle:handle];
+ FlutterCallbackInformation *info = [FlutterCallbackCache lookupCallbackInformation:handle];
+ NSAssert(info != nil, @"failed to find callback");
+ NSString *entrypoint = info.callbackName;
+ NSString *uri = info.callbackLibraryPath;
+ [_headlessRunner runWithEntrypointAndLibraryUri:entrypoint libraryUri:uri];
+ [_registrar addMethodCallDelegate:self channel:_callbackChannel];
+}
+
+- (void)registerGeofence:(NSArray *)arguments {
+ NSLog(@"RegisterGeofence: %@", arguments);
+ int64_t callbackHandle = [arguments[0] longLongValue];
+ NSString *identifier = arguments[1];
+ double latitude = [arguments[2] doubleValue];
+ double longitude = [arguments[3] doubleValue];
+ double radius = [arguments[4] doubleValue];
+ int64_t triggerMask = [arguments[5] longLongValue];
+
+ CLCircularRegion *region =
+ [[CLCircularRegion alloc] initWithCenter:CLLocationCoordinate2DMake(latitude, longitude)
+ radius:radius
+ identifier:identifier];
+ region.notifyOnEntry = ((triggerMask & 0x1) != 0);
+ region.notifyOnExit = ((triggerMask & 0x2) != 0);
+
+ [self setCallbackHandleForRegionId:callbackHandle regionId:identifier];
+ [self->_locationManager startMonitoringForRegion:region];
+}
+
+- (BOOL)removeGeofence:(NSArray *)arguments {
+ NSLog(@"RemoveGeofence: %@", arguments);
+ NSString *identifier = arguments[0];
+ for (CLRegion *region in [self->_locationManager monitoredRegions]) {
+ if ([region.identifier isEqual:identifier]) {
+ [self->_locationManager stopMonitoringForRegion:region];
+ [self removeCallbackHandleForRegionId:identifier];
+ return YES;
+ }
+ }
+ return NO;
+}
+
+- (int64_t)getCallbackDispatcherHandle {
+ id handle = [_persistentState objectForKey:@"callback_dispatcher_handle"];
+ if (handle == nil) {
+ return 0;
+ }
+ return [handle longLongValue];
+}
+
+- (void)setCallbackDispatcherHandle:(int64_t)handle {
+ [_persistentState setObject:[NSNumber numberWithLongLong:handle]
+ forKey:@"callback_dispatcher_handle"];
+}
+
+- (NSMutableDictionary *)getRegionCallbackMapping {
+ const NSString *key = kCallbackMapping;
+ NSMutableDictionary *callbackDict = [_persistentState dictionaryForKey:key];
+ if (callbackDict == nil) {
+ callbackDict = @{};
+ [_persistentState setObject:callbackDict forKey:key];
+ }
+ return [callbackDict mutableCopy];
+}
+
+- (void)setRegionCallbackMapping:(NSMutableDictionary *)mapping {
+ const NSString *key = kCallbackMapping;
+ NSAssert(mapping != nil, @"mapping cannot be nil");
+ [_persistentState setObject:mapping forKey:key];
+}
+
+- (int64_t)getCallbackHandleForRegionId:(NSString *)identifier {
+ NSMutableDictionary *mapping = [self getRegionCallbackMapping];
+ id handle = [mapping objectForKey:identifier];
+ if (handle == nil) {
+ return 0;
+ }
+ return [handle longLongValue];
+}
+
+- (void)setCallbackHandleForRegionId:(int64_t)handle regionId:(NSString *)identifier {
+ NSMutableDictionary *mapping = [self getRegionCallbackMapping];
+ [mapping setObject:[NSNumber numberWithLongLong:handle] forKey:identifier];
+ [self setRegionCallbackMapping:mapping];
+}
+
+- (void)removeCallbackHandleForRegionId:(NSString *)identifier {
+ NSMutableDictionary *mapping = [self getRegionCallbackMapping];
+ [mapping removeObjectForKey:identifier];
+ [self setRegionCallbackMapping:mapping];
+}
+
+@end
diff --git a/ios/geofencing.podspec b/ios/geofencing.podspec
new file mode 100644
index 0000000..6c29211
--- /dev/null
+++ b/ios/geofencing.podspec
@@ -0,0 +1,21 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
+#
+Pod::Spec.new do |s|
+ s.name = 'geofencing'
+ s.version = '0.0.1'
+ s.summary = 'A new flutter plugin project.'
+ s.description = <<-DESC
+A new flutter plugin project.
+ DESC
+ s.homepage = 'http://example.com'
+ s.license = { :file => '../LICENSE' }
+ s.author = { 'Your Company' => 'email@example.com' }
+ s.source = { :path => '.' }
+ s.source_files = 'Classes/**/*'
+ s.public_header_files = 'Classes/**/*.h'
+ s.dependency 'Flutter'
+
+ s.ios.deployment_target = '8.0'
+end
+
diff --git a/lib/geofencing.dart b/lib/geofencing.dart
new file mode 100644
index 0000000..db6ec8a
--- /dev/null
+++ b/lib/geofencing.dart
@@ -0,0 +1,9 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+export 'package:geofencing/src/geofencing.dart'
+ hide geofenceEventToInt, intToGeofenceEvent;
+export 'package:geofencing/src/location.dart' hide locationFromList;
+export 'package:geofencing/src/platform_settings.dart'
+ hide platformSettingsToArgs;
diff --git a/lib/src/callback_dispatcher.dart b/lib/src/callback_dispatcher.dart
new file mode 100644
index 0000000..1f2e208
--- /dev/null
+++ b/lib/src/callback_dispatcher.dart
@@ -0,0 +1,37 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+import 'package:geofencing/src/geofencing.dart';
+import 'package:geofencing/src/location.dart';
+
+void callbackDispatcher() {
+ const MethodChannel _backgroundChannel =
+ MethodChannel('plugins.flutter.io/geofencing_plugin_background');
+ WidgetsFlutterBinding.ensureInitialized();
+
+ _backgroundChannel.setMethodCallHandler((MethodCall call) async {
+ print("Callback Dispatcher Invoked: ${call.arguments}");
+ final List args = call.arguments;
+ final Function callback = PluginUtilities.getCallbackFromHandle(
+ CallbackHandle.fromRawHandle(args[0]));
+ assert(callback != null);
+ final List triggeringGeofences = args[1].cast();
+ final List locationList = [];
+ // 0.0 becomes 0 somewhere during the method call, resulting in wrong
+ // runtime type (int instead of double). This is a simple way to get
+ // around casting in another complicated manner.
+ args[2]
+ .forEach((dynamic e) => locationList.add(double.parse(e.toString())));
+ final Location triggeringLocation = locationFromList(locationList);
+ final GeofenceEvent event = intToGeofenceEvent(args[3]);
+ callback(triggeringGeofences, triggeringLocation, event);
+ });
+ print('GeofencingPlugin dispatcher started');
+ _backgroundChannel.invokeMethod('GeofencingService.initialized');
+}
diff --git a/lib/src/geofencing.dart b/lib/src/geofencing.dart
new file mode 100644
index 0000000..0bc8cef
--- /dev/null
+++ b/lib/src/geofencing.dart
@@ -0,0 +1,160 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:ui';
+
+import 'package:flutter/services.dart';
+
+import 'package:geofencing/src/callback_dispatcher.dart';
+import 'package:geofencing/src/location.dart';
+import 'package:geofencing/src/platform_settings.dart';
+
+const int _kEnterEvent = 1;
+const int _kExitEvent = 2;
+const int _kDwellEvent = 4;
+
+/// Valid geofencing events.
+///
+/// Note: `GeofenceEvent.dwell` is not supported on iOS.
+enum GeofenceEvent { enter, exit, dwell }
+
+// Internal.
+int geofenceEventToInt(GeofenceEvent e) {
+ switch (e) {
+ case GeofenceEvent.enter:
+ return _kEnterEvent;
+ case GeofenceEvent.exit:
+ return _kExitEvent;
+ case GeofenceEvent.dwell:
+ return _kDwellEvent;
+ default:
+ throw UnimplementedError();
+ }
+}
+
+// TODO(bkonyi): handle event masks
+// Internal.
+GeofenceEvent intToGeofenceEvent(int e) {
+ switch (e) {
+ case _kEnterEvent:
+ return GeofenceEvent.enter;
+ case _kExitEvent:
+ return GeofenceEvent.exit;
+ case _kDwellEvent:
+ return GeofenceEvent.dwell;
+ default:
+ throw UnimplementedError();
+ }
+}
+
+/// A circular region which represents a geofence.
+class GeofenceRegion {
+ /// The ID associated with the geofence.
+ ///
+ /// This ID is used to identify the geofence and is required to delete a
+ /// specific geofence.
+ final String id;
+
+ /// The location of the geofence.
+ final Location location;
+
+ /// The radius around `location` that will be considered part of the geofence.
+ final double radius;
+
+ /// The types of geofence events to listen for.
+ ///
+ /// Note: `GeofenceEvent.dwell` is not supported on iOS.
+ final List triggers;
+
+ /// Android specific settings for a geofence.
+ final AndroidGeofencingSettings androidSettings;
+
+ GeofenceRegion(
+ this.id, double latitude, double longitude, this.radius, this.triggers,
+ {AndroidGeofencingSettings androidSettings})
+ : location = Location(latitude, longitude),
+ androidSettings = (androidSettings ?? AndroidGeofencingSettings());
+
+ List _toArgs() {
+ final int triggerMask = triggers.fold(
+ 0, (int trigger, GeofenceEvent e) => (geofenceEventToInt(e) | trigger));
+ final List args = [
+ id,
+ location.latitude,
+ location.longitude,
+ radius,
+ triggerMask
+ ];
+ if (Platform.isAndroid) {
+ args.addAll(platformSettingsToArgs(androidSettings));
+ }
+ return args;
+ }
+}
+
+class GeofencingManager {
+ static const MethodChannel _channel =
+ MethodChannel('plugins.flutter.io/geofencing_plugin');
+ static const MethodChannel _background =
+ MethodChannel('plugins.flutter.io/geofencing_plugin_background');
+
+ /// Initialize the plugin and request relevant permissions from the user.
+ static Future initialize() async {
+ final CallbackHandle callback =
+ PluginUtilities.getCallbackHandle(callbackDispatcher);
+ await _channel.invokeMethod('GeofencingPlugin.initializeService',
+ [callback.toRawHandle()]);
+ }
+
+ /// Promote the geofencing service to a foreground service.
+ ///
+ /// Will throw an exception if called anywhere except for a geofencing
+ /// callback.
+ static Future promoteToForeground() async =>
+ await _background.invokeMethod('GeofencingService.promoteToForeground');
+
+ /// Demote the geofencing service from a foreground service to a background
+ /// service.
+ ///
+ /// Will throw an exception if called anywhere except for a geofencing
+ /// callback.
+ static Future demoteToBackground() async =>
+ await _background.invokeMethod('GeofencingService.demoteToBackground');
+
+ /// Register for geofence events for a [GeofenceRegion].
+ ///
+ /// `region` is the geofence region to register with the system.
+ /// `callback` is the method to be called when a geofence event associated
+ /// with `region` occurs.
+ ///
+ /// Note: `GeofenceEvent.dwell` is not supported on iOS. If the
+ /// `GeofenceRegion` provided only requests notifications for a
+ /// `GeofenceEvent.dwell` trigger on iOS, `UnsupportedError` is thrown.
+ static Future registerGeofence(
+ GeofenceRegion region,
+ void Function(List id, Location location, GeofenceEvent event)
+ callback) async {
+ if (Platform.isIOS &&
+ region.triggers.contains(GeofenceEvent.dwell) &&
+ (region.triggers.length == 1)) {
+ throw UnsupportedError("iOS does not support 'GeofenceEvent.dwell'");
+ }
+ final List args = [
+ PluginUtilities.getCallbackHandle(callback).toRawHandle()
+ ];
+ args.addAll(region._toArgs());
+ await _channel.invokeMethod('GeofencingPlugin.registerGeofence', args);
+ }
+
+ /// Stop receiving geofence events for a given [GeofenceRegion].
+ static Future removeGeofence(GeofenceRegion region) async =>
+ (region == null) ? false : await removeGeofenceById(region.id);
+
+ /// Stop receiving geofence events for an identifier associated with a
+ /// geofence region.
+ static Future removeGeofenceById(String id) async => await _channel
+ .invokeMethod('GeofencingPlugin.removeGeofence', [id]);
+}
diff --git a/lib/src/location.dart b/lib/src/location.dart
new file mode 100644
index 0000000..39b37d8
--- /dev/null
+++ b/lib/src/location.dart
@@ -0,0 +1,22 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Internal.
+Location locationFromList(List l) => Location._fromList(l);
+
+/// A simple representation of a geographic location.
+class Location {
+ final double latitude;
+ final double longitude;
+
+ const Location(this.latitude, this.longitude);
+
+ Location._fromList(List l)
+ : assert(l.length == 2),
+ latitude = l[0],
+ longitude = l[1];
+
+ @override
+ String toString() => '($latitude, $longitude)';
+}
diff --git a/lib/src/platform_settings.dart b/lib/src/platform_settings.dart
new file mode 100644
index 0000000..fa0231c
--- /dev/null
+++ b/lib/src/platform_settings.dart
@@ -0,0 +1,33 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:geofencing/src/geofencing.dart';
+
+// Internal.
+List platformSettingsToArgs(AndroidGeofencingSettings s) =>
+ s._toArgs();
+
+class AndroidGeofencingSettings {
+ List initialTrigger;
+ int expirationDuration;
+ int loiteringDelay;
+ int notificationResponsiveness;
+
+ AndroidGeofencingSettings(
+ {this.initialTrigger = const [GeofenceEvent.enter],
+ this.loiteringDelay = 0,
+ this.expirationDuration = -1,
+ this.notificationResponsiveness = 0});
+
+ List _toArgs() {
+ final int initTriggerMask = initialTrigger.fold(
+ 0, (int trigger, GeofenceEvent e) => (geofenceEventToInt(e) | trigger));
+ return [
+ initTriggerMask,
+ expirationDuration,
+ loiteringDelay,
+ notificationResponsiveness
+ ];
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..7867ac1
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,17 @@
+name: geofencing
+description: A geofencing plugin for Flutter applications.
+version: 0.0.1
+author: Ben Konyi
+homepage: https://github.com/flutter/plugins
+
+environment:
+ sdk: ">=2.0.0-dev.68.0 <3.0.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+
+flutter:
+ plugin:
+ androidPackage: io.flutter.plugins.geofencing
+ pluginClass: GeofencingPlugin
\ No newline at end of file