使用Google Geofence API

Google Geofence API is used when you want to notify users if they are near the specific area in the world (such as Shopping Malls, Airport, Restaurants etc.). For example, the customer has installed an online shopping app and if they are near one of the stores that the app has a tie up with, then store could generate a notification in the app to let the customer know that the store near them has a special offer running for them. This way you can engage more users.

如果您想通知用户附近世界的特定区域(例如购物中心,机场,餐馆等),请使用Google Geofence API。 例如,客户已安装了一个在线购物应用程序,并且如果他们在该应用程序与之相关的商店之一附近,则商店可以在该应用程序中生成一条通知,以使客户知道他们附近的商店有一个商店。为他们运行的特别优惠。 这样,您可以吸引更多用户。

获取Google Maps API密钥 (Get the Google Maps API Key)

To use the Geofence API, you’ll have to obtain the Google Maps API key using the following set of steps.

要使用Geofence API,您必须使用以下步骤获取Google Maps API密钥。

  • Click on THIS link to open the Google Cloud Platform, if you’re not signed in, then please sign in first and create the project.

    点击链接以打开Goog​​le Cloud Platform,如果您尚未登录,请先登录并创建项目。

Image for post

You can give any name to this project or you can just leave it as it is and click on CREATE now.

您可以给这个项目起任何名字,也可以直接保留它,然后单击CREATE

  • Now ensure that you’ve selected the newly created project.

    现在,确保您选择了新创建的项目。
Image for post
  • Now click on the menu icon on left and select the APIs & Services and then choose Library from the sub-list.

    现在,单击左侧的菜单图标,然后选择API和服务 ,然后从子列表中选择

  • Search Maps SDK for Android and enable this API (don’t forget to enable API because it's important)

    搜索适用于Android的Maps SDK启用此API(不要忘记启用API,因为它很重要)

  • Now again click on the menu icon on left and choose Credentials and then + CREATE CREDENTIALS and finally click on API Key

    现在再次单击左侧的菜单图标,然后依次选择“ 凭据”和“ +创建 凭据” ,最后单击“ API密钥”

  • Copy this API Key and paste it inside the string.xml file

    复制此API密钥并将其粘贴到string.xml文件中

我们将要建立的 (What we’re going to build)

We’re going to build an application which will allow you to set your location (or specific area to be precise) on a map. And when you reach the specified location, your app will notify you with a message you defined at the time when you set the location.

我们将构建一个应用程序,使您可以在地图上设置位置(或精确的特定区域)。 而且,当您到达指定位置时,您的应用程序会通过设置位置时定义的消息通知您。

In the end, you’ll have an app which will look similar to this.

最后,您将拥有一个类似于此的应用程序。

Image for post

To set the location on the map, you can drag the marker on your desired location. Once you specified your location, you can proceed by clicking CONTINUE button.

要在地图上设置位置,您可以将标记拖动到所需的位置。 指定位置后,可以单击“ 继续”按钮继续

Image for post

Are you excited now?, I’m sure you’ll be.

你现在兴奋吗?我确定你会的。

我们将学到什么? (What we’ll learn?)

  • Google Map API

    Google Map API
  • Google Geofence API

    Google Geofence API
  • Service

    服务
  • Broadcast Receiver

    广播接收器
  • Creating Notification

    创建通知
  • Storing and retrieving custom objects in Shared-preference

    在“共享首选项”中存储和检索自定义对象

Now let’s quickly set up our project and proceed.

现在,让我们快速设置我们的项目并继续。

设定Android专案 (Setup Android Project)

  • Create a New Android Project, select the empty activity and give any name you want to your project.

    创建一个新的Android项目,选择空白活动,然后为您的项目命名。
  • Add the following dependencies in your app-level build.gradle

    在应用程序级别的build.gradle中添加以下依赖

implementation 'com.google.android.gms:play-services-location:16.0.0'implementation 'com.google.android.gms:play-services-maps:16.0.0'implementation 'com.google.code.gson:gson:2.8.5'implementation "com.google.android.material:material:1.1.0-alpha02"
  • If you encounter the following error in your activity.xml file

    如果您在activity.xml文件中遇到以下错误
Image for post

Then add the following line inside your build.gradle

然后在build.gradle中添加以下行

android{ defaultConfig {
vectorDrawables.useSupportLibrary = true}}
  • Add the following permissions in your AndroidManifest.xml file

    在您的AndroidManifest.xml文件中添加以下权限

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  • Now add the below meta-data tag in your AndroidManifest.xml file inside the <application> tag, we’ll add the API_KEY here later on.

    现在,在您的AndroidManifest.xml文件中的<application>标记内添加以下元数据标记,我们稍后将在此处添加API_KEY

<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="API_KEY"
/>
  • Finally, sync the project and setup done.

    最后,同步项目并完成设置。

Let’s get quickly familiar with some android components used in this app

让我们快速熟悉此应用程序中使用的一些android组件

Service: A service is an android component which is used to perform background tasks. A task may be of any kind such as uploading photos, syncing user data on server and generating notifications etc.

服务:服务是用于执行后台任务的android组件。 任务可以是任何类型,例如上传照片,在服务器上同步用户数据以及生成通知等。

Broadcast Receiver: A broadcast receiver is also a fundamental component of android, it comes handy when you want to perform some actions in your app which are based on future events. For instance, you only want to upload user data to your server when the phone is connected to the WIFI and another example is, you want to start the background service when the user restarts his phone.

广播接收器:广播接收器也是android的基本组件,当您希望基于未来事件在应用中执行一些操作时,它会派上用场。 例如,您只想在手机连接到WIFI时将用户数据上传到服务器,而另一个示例是,您想在用户重启手机时启动后台服务。

PendingIntent: PendingIntent is similar to normal intent except that it will not execute immediately.

PendingIntent: PendingIntent与普通意图类似,不同之处在于它不会立即执行。

让我们了解我们的课程 (Let’s understand our classes)

I’ve all the classes, utility functions and constants under the root package.

我在root包下有所有类,实用程序函数和常量。

Image for post

Let’s understand them one by one.

让我们一一理解它们。

  • MainActivity.kt: This is our home screen which will show you Google Map and two action buttons on the bottom right side. The first button will allow you to zoom in on the map and the plus action button will open new activity which is ReminderActivity.kt, on this screen you can choose your location and set the message and save the reminder.

    MainActivity.kt:这是我们的主屏幕,它将在右下方显示Google Map和两个操作按钮。 第一个按钮将允许您放大地图,加号操作按钮将打开新活动ReminderActivity.kt ,在该屏幕上,您可以选择位置并设置消息并保存提醒。

  • ReminderActivity.kt: As I explained this activity will allow you to set the location and save the reminder in the shared-preference. When you choose the location and save it, you’ll see the circle around the location you specified. This circle is boundary or radius you’ll define in your code. The larger circle means, it will cover the larger area. And as you enter into this area, you’ll be notified by a notification.

    ReminderActivity.kt:正如我所解释的,此活动将使您可以设置位置并将提醒保存在共享首选项中。 选择位置并保存后,您会看到指定位置周围的圆圈。 该圆是您将在代码中定义的边界或半径。 较大的圆圈表示它将覆盖较大的区域。 当您进入该区域时,您会收到通知通知。

  • MyBroadcast Receiver: This is a broadcast receiver which will be fired when the user reaches to the specified location. When this broadcast receiver fires, it will execute the background service.

    MyBroadcast接收器:这是一个广播接收器,当用户到达指定位置时将被触发。 当此广播接收器触发时,它将执行后台服务。

  • GeofenceTransitionService.kt: This is a service which will be executed by our broadcast receiver and when this executes it generates the notification to let user know he has reached his destination.

    GeofenceTransitionService.kt:这是一项服务,将由我们的广播接收器执行,并且在执行该服务时,它将生成通知,让用户知道他已经到达目的地。

  • MyReminder.kt: This is a data class and in its constructor, I’m accepting four parameters and they’re unique id, latitude, longitude, radium and a message. We’ll keep this information in shared-preference and at the same time, we will set the Pending Intent to generate the notification.

    MyReminder.kt:这是一个数据类,在其构造函数中,我接受四个参数,它们是唯一的id,纬度,经度,镭和消息。 我们将这些信息保留在共享首选项中,同时,我们将设置“待处理的意图”以生成通知。

  • MyReminderRepository.kt: As you know repository helps us to get the core methods of the application to perform actions we defined for our app. So only this class is responsible for adding a new reminder, removing reminder and setting Pending Intents. We will always create an object of this class and perform the actions.

    MyReminderRepository.kt:众所周知,存储库可帮助我们获取应用程序的核心方法,以执行为应用程序定义的操作。 因此,只有此类负责添加新的提醒,删除提醒和设置待处理的意图。 我们将始终创建此类的对象并执行操作。

  • Utils.kt: This is our utility class which contains four methods.vectorToBitmap() method converts the vector image to bitmap, showReminderInMap() method displays the reminder on the map, this is the method which draws the circle around the specified location. And sendNotification() method creates the notification, this method is called by our background service. The last method is getUniqueId() which obviously returns the unique id.

    Utils.kt:这是我们的实用程序类,其中包含四个方法。 vectorToBitmap()方法将矢量图像转换为位图, showReminderInMap()方法在地图上显示提醒,这是一种在指定位置周围绘制圆的方法。 然后sendNotification()方法创建通知,此方法由我们的后台服务调用。 最后一个方法是getUniqueId() ,它显然返回唯一的ID。

  • AppConstants.kt: This class contains all the constants used for this app. This class also contains a service method and two methods to call the MainActivity.kt and ReminderActivity.kt activities.

    AppConstants.kt:此类包含用于此应用程序的所有常量。 此类还包含一个服务方法和两个方法来调用MainActivity.ktReminderActivity.kt活动。

Enough talking? let’s get back to business…

够说话吗? 让我们回到正题...

When you create a project, you will have a default ActivityMain.kt class. I want you to create one more class and name it as ReminderActivity.kt. In this class extend AppCompatActivity() class and implement OnMapReadyCallback the interface of Google Map and implement its method onMapReady(). For now, leave everything as it is.

创建项目时,将具有默认的ActivityMain.kt类。 我希望您再创建一个类并将其命名为ReminderActivity.kt。 在此类中,扩展AppCompatActivity()类并实现OnMapReadyCallback Google Map的接口,并实现其方法onMapReady() 。 现在,保留一切。

Now we’ll create our AppConstants class and define all constants and few methods.

现在,我们将创建AppConstants类,并定义所有常量和一些方法。

In this class, we defined our constants and three methods, newIntentForMainActivity() method will call the MainActivity.kt class and newIntentForReminderActivity() will call the ReminderActivity.kt class. enqueueWork() method will be called by our broadcast receiver to generate the notification.Tip: All constants and methods are defined inside the companion object, and this is because whenever we need to call any of these methods, we won’t have to create object of this class.

在此类中,我们定义了常量和三个方法, newIntentForMainActivity()方法将调用MainActivity.kt类,而newIntentForReminderActivity()将调用ReminderActivity.kt类。 我们的广播接收器将调用enqueueWork()方法以生成通知。 提示:所有常量和方法都在companion object内定义,这是因为每当需要调用这些方法中的任何一个时,我们都不必创建此类的对象。

Now we’ll create Utils.kt and add the following methods

现在,我们将创建Utils.kt并添加以下方法

vectorToBimap() (vectorToBimap())

When we’ll call this method, we have to pass two parameters i.e context.resources and an id of our image which needs to converted into bitmap. The context.resources returns a resources instance for your application. Inside this method, I got the drawable object, created the Bitmap and Canvas objects. We now have our image in vectorDrawable and we’ll convert this object into bitmap, then draw bitmap on canvas and set its bounds and return.

调用此方法时,必须传递两个参数,即context.resources和需要转换为位图的图像ID。 context. resources context. resources 返回您的应用程序的资源实例。 在此方法内部,我得到了drawable对象,创建了BitmapCanvas对象。 现在,我们将图像保存在vectorDrawable ,并将该对象转换为bitmap ,然后在画布上绘制bitmap并设置其边界并返回。

Tip: If you’re not familiar with Canvas, then you can think of it as a blank piece of paper where you can draw any shape.

提示:如果您不熟悉Canvas ,则可以将其视为一张空白纸,可以在其中绘制任何形状。

showReminderInMap() (showReminderInMap())

showReminderInMap() method takes context, GoogleMap object and MyReminder class object as an argument and checks if myReminder has a value, then it proceeds further otherwise it does nothing. Inside the if statement I’m getting the latitude and longitude from my myReminder object and casting it to LatLng and in the second if statement, I’m drawing the circle around the specified location. In general, this method is used to draw the circle on a specified location.

showReminderInMap()方法将上下文, GoogleMap对象和MyReminder类对象作为参数,并检查myReminder是否具有值,然后继续进行,否则不执行任何操作。 在if语句内部,我从myReminder对象获取纬度和经度,并将其强制转换为LatLng ,在第二个if语句中,我在指定位置周围绘制了圆圈。 通常,此方法用于在指定位置绘制圆。

sendNotification() (sendNotification())

The sendNotification() method is pretty straightforward, it does nothing except creating the notification. This method is called from the service to show the notification.

sendNotification()方法非常简单,除了创建通知外,它什么也不做。 从服务调用此方法以显示通知。

MyBroadcastReceiver.kt类 (MyBroadcastReceiver.kt class)

Now create this class in your root directory and also add this broadcast receiver in your AndroidManifest.xml file.

现在,在您的根目录中创建此类,并在AndroidManifest.xml文件中添加此广播接收器。

<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true"
/>

This class extends the BroadcastReceiver() class which is an abstract class and implements its onReceive() method. As the onReceive() method is executed, it will execute the enqueueWork() method of AppConstants.kt.

该类扩展了BroadcastReceiver()类,该类是一个抽象类,并实现其onReceive()方法。 在执行onReceive()方法时,它将执行AppConstants.kt.enqueueWork()方法AppConstants.kt.

This receiver would be registered viaPendingIntent when you will add the reminder.

添加提醒时,该接收者将通过PendingIntent注册。

GeofenceTransitionService.kt (GeofenceTransitionService.kt)

Create this class and declare it in the AndroidManifest.xml file

创建此类并在AndroidManifest.xml文件中声明

<service
android:name=".GeofenceTransitionService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE"
/>

Note: If a job service is declared in the manifest but not protected with BIND_JOB_SERVICE permission, that service will be ignored by the system.

注意:如果作业服务在清单中声明,但未受到BIND_JOB_SERVICE权限的保护,则该服务将被系统忽略。

This is a service which extends the JobIntentService class, IntentService or JobIntentService is a service which runs in the background on a separate thread and it gets destroyed when the job is done. To use this class we’ll have to implement its required method onHandleWork() .

这是扩展JobIntentService类的服务,IntentService或JobIntentService是在单独的线程中在后台运行的服务,当作业完成时会被销毁。 要使用此类,我们必须实现其必需的方法onHandleWork()

class GeofenceTransitionService : JobIntentService() {override fun onHandleWork(intent: Intent) {val geofencingEvent = GeofencingEvent.fromIntent(intent)repositoryMy = MyReminderRepository(this)if (geofencingEvent.hasError()) {
Log.e(AppConstants.LOG_TAG, "An error occured")return}
handleEvent(geofencingEvent)
}
}

Inside this method, we can do our task. I’m getting the object of GeofencingEvent class inside this method, if there is an issue, we show the error message otherwise we call the handleEvent() method.

在此方法内部,我们可以完成任务。 我在此方法中获取了GeofencingEvent类的对象,如果存在问题,则显示错误消息,否则我们将调用handleEvent()方法。

private fun handleEvent(event: GeofencingEvent) {if (event.geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {val reminder = getFirstReminder(event.triggeringGeofences)val message = reminder?.message
val
latLng = reminder?.latLng
if
(message != null && latLng != null) {sendNotification(this, message, latLng)
}
}
}

In this method, we first use the GeofencingEvent object and checks whether the user enters in the specified area or not, if it then gets the reminder object from the repository, retrieves its data and send the notification.

在这种方法中,我们首先使用GeofencingEvent对象,并检查用户是否进入指定区域,然后从存储库中获取提醒对象,检索其数据并发送通知。

private fun getFirstReminder(triggeringGeofences: List<Geofence>): MyReminder? {val firstGeofence = triggeringGeofences[0]return repositoryMy.get(firstGeofence.requestId)
}

getFirstReminder() method returns the object of MyReminder class from the MyRepository class.

getFirstReminder()方法从MyRepository类返回MyReminder类的对象。

Below is the full code for this service class

以下是此服务类的完整代码

Tip: lateinit is a keyword in Kotlin which frees you from not to initialise it at time of declaration but it should be initialised before its use otherwise, you will run into the NullPointerException.

提示: lateinit 是Kotlin中的关键字,它使您无需在声明时对其进行初始化,但应在使用它之前进行初始化,否则,您将遇到NullPointerException

MyReminderRepository.kt (MyReminderRepository.kt)

You can think of this class as a local server which means it serves us everything we needed to perform any action on the map such as adding a reminder or removing reminder and maintaining all reminders.

您可以将此类视为本地服务器,这意味着它为我们提供了在地图上执行任何操作(例如添加提醒或删除提醒以及维护所有提醒)所需的一切。

The following code snippet creates an object of GeofencingClient which helps you to manipulate the geofences.

以下代码段创建了一个GeofencingClient对象,该对象可帮助您操纵地理围栏。

private val geofencingClient = LocationServices.getGeofencingClient(context)

Here we’re getting the SharedPreference object and Gson object

在这里,我们得到了SharedPreference对象和Gson对象

private val preferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)private val gson = Gson()

In the SharedPreference, we’ll store the reminders and using Gson object we’ll convert json string to Java object and vice-versa. Since SharedPreference can hold only primitive values in the form of key-value pairs but we want to store our class object, so how it is possible?. Obviously, we can convert our object to json string and store it in a SharedPreference, whenever we need to use it, we will convert it into a real object.

SharedPreference ,我们将存储提醒,并使用Gson对象将json字符串转换为Java对象,反之亦然。 由于SharedPreference只能保存键-值对形式的原始值,但是我们想存储我们的类对象,所以怎么可能呢? 显然,我们可以将对象转换为json字符串,并将其存储在SharedPreference中,每当需要使用它时,我们都会将其转换为真实对象。

buildGeofence(): (buildGeofence():)

The combination of latitude, longitude and a radius is known as geofence. It’s a circular area at a specific location on the map. Whenever users enter this area, the app will trigger particular behaviour.

纬度,经度和半径的组合称为地理围栏。 这是地图上特定位置的圆形区域。 每当用户进入该区域时,该应用程序都会触发特定行为。

private fun buildGeofence(myReminder: MyReminder): Geofence? {val latitude = myReminder.latLng?.latitude
val
longitude = myReminder.latLng?.longitude
val
radius = myReminder.radius
if
(latitude != null && longitude != null && radius != null) {return Geofence.Builder()
.setRequestId(myReminder.id)
.setCircularRegion(
latitude,
longitude,
radius.toFloat()
)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.build()
}return null}

Look at the code, we’re building the geofence using Geofence.Builder().

看一下代码,我们正在使用Geofence.Builder()构建geofence。

In the method setRequestId(), I’m passing unique id since every geofence is unique in its way, you will get this id from your model class.

在方法setRequestId()中 ,我传递了唯一的ID,因为每种地理围栏都是唯一的,因此您将从模型类中获得此ID。

In the setCircularRegion(), I’m setting my latitude, longitude and radius, all these parameters together form a circle on a specific location.

setCircularRegion()中 ,设置我的纬度,经度和半径, 所有这些参数一起在特定位置上形成一个圆

The setTransitionTypes() defines the transition type, you can use GEOFENCE_TRANSITION_ENTER to trigger an event when the user enters into the defined area.

setTransitionTypes()定义过渡类型,可以使用GEOFENCE_TRANSITION_ENTER 当用户进入定义的区域时触发事件。

The setExpirationDuration() method sets the expiry of the geofence, we’re using NEVER_EXPIRE which means that it will never expire until the user removes it.

setExpirationDuration()方法设置地理围栏的到期时间,我们使用的是NEVER_EXPIRE 这意味着它永远不会过期,除非用户将其删除。

buildFencingRequest(): (buildFencingRequest():)

You’ll use this method to build the geofence request.

您将使用此方法来构建地理围栏请求。

private fun buildGeofencingRequest(geofence: Geofence): GeofencingRequest {return GeofencingRequest.Builder()
.setInitialTrigger(0)
.addGeofences(listOf(geofence))
.build()
}

Here you’re setting setInitialTrigger() to 0 which means that you don’t want your app to trigger an event when the app is already inside the geofence. And the next addGeofences() method adds the request to geofence.

在这里,您将setInitialTrigger()设置为0,这意味着当应用程序已经在地理围栏内时,您不希望您的应用程序触发事件。 接下来的addGeofences()方法将请求添加到geofence。

建筑地理围栏待定意图: (Building geofence PendingIntent:)

As you can see in the code snippet, we’re passing MyBroadcastReceiver in the intent object which means that when this pending intent is called, it will call broadcast receiver and this receiver will call GeofenceTransitionService.

如您在代码片段中所看到的,我们在intent对象中传递MyBroadcastReceiver,这意味着当调用该未决的intent时,它将调用广播接收器,而该接收器将调用GeofenceTransitionService。

private val geofencePendingIntent: PendingIntent by lazy {
val
intent = Intent(context, MyBroadcastReceiver::class.java)
PendingIntent.getBroadcast(context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT)}

This code will only be executed when GEOFENCE_TRANSITION_ENTER event is triggered.

仅在触发GEOFENCE_TRANSITION_ENTER事件时才执行此代码。

Tip: by lazy property, the value only gets computed when this is used for the first time.

提示:通过 惰性属性,仅在首次使用该值时才计算该值。

加(): (add():)

This add() method adds the reminder in the SharedPreference and also adds the request to the geofence.

add()方法将提醒添加到SharedPreference中,并且还将请求添加到地理围栏。

fun add(myReminder: MyReminder, success: () -> Unit, failure: (error: String) -> Unit)
{val geofence = buildGeofence(myReminder)if (geofence != null && ContextCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {geofencingClient.addGeofences(buildGeofencingRequest(geofence), geofencePendingIntent)
.addOnSuccessListener {saveAll(getAll() + myReminder)
success()}.addOnFailureListener {failure("An error occured")}}
}

First of all, we’re checking the permission if it is granted, then we proceed, and next, we’re creating the geofence using the geofencingClient if it is successful, then only we’ll save in shared preference, otherwise report an error message.

首先,我们要检查是否授予了该权限,然后继续进行操作;接下来,如果成功,我们将使用geofencingClient创建地理围栏 ,然后仅保存共享的首选项,否则报告错误信息。

去掉(): (remove():)

This method simply removes the reminder from the list.

此方法只是从列表中删除提醒。

fun remove(myReminder: MyReminder) {val list = getAll() - myReminder
saveAll(list)
}

保存全部(): (saveAll():)

This method adds all the reminders in the list in shared-preference.

此方法将所有提醒添加到共享首选项列表中。

private fun saveAll(list: List<MyReminder>) {preferences.edit()
.putString(REMINDERS, gson.toJson(list))
.apply()
}

Notice we’re converting the list to Json string, then we’re storing it.

注意,我们将列表转换为Json字符串,然后将其存储。

得到所有(): (getAll():)

This method returns the list of all reminders we stored in the shared-preference.

此方法返回我们存储在共享首选项中的所有提醒的列表。

fun getAll(): List<MyReminder> {if (preferences.contains(REMINDERS)) {val remindersString = preferences.getString(REMINDERS, null)val arrayOfReminders = gson.fromJson(
remindersString,
Array<MyReminder>::class.java)if (arrayOfReminders != null) {return arrayOfReminders.toList()
}
}return listOf()
}

getLast(): (getLast():)

This function returns the reminder added in the list.

此函数返回添加在列表中的提醒。

fun getLast() = getAll().lastOrNull()

Notice this function first calls the getAll() method which returns a list and we call lastOrNull() function which is a method of List class and this method returns the last value.

注意,此函数首先调用getAll()方法,该方法返回一个列表,而我们调用lastOrNull()函数,该方法是List类的一个方法,该方法返回最后一个值。

得到(): (get():)

This method returns the first reminder from the list if the list is not null.

如果列表不为null.则此方法从列表返回第一个提醒null.

fun get(requestId: String?) = getAll().firstOrNull { it.id == requestId }

{it.id == requestId } is a lambda expression which is performing null checking. When there is only a single parameter inside the lambda expression, then it can be used.

{it.id == requestId }是一个lambda表达式,正在执行空检查。 如果lambda表达式中只有一个参数,则可以使用it

Complete code for MyReminderRepository.kt

MyReminderRepository.kt的完整代码

ReminderActivity.kt: (ReminderActivity.kt:)

This activity allows us to add the new reminder on the specified location.

通过此活动,我们可以在指定位置添加新的提醒。

First of all, create these objects in this activity.

首先,在此活动中创建这些对象。

private lateinit var map: GoogleMaplateinit var myRepository: MyReminderRepositoryprivate var reminder = MyReminder(latLng = null, radius = null, message = null)

Now paste the following code inside your onCreate() method

现在将以下代码粘贴到onCreate()方法中

override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_reminder)myRepository = MyReminderRepository(this)val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
instructionTitle.visibility = View.GONEradiusDescription.visibility = View.GONEmessage.visibility = View.GONE}

In this onCreate() method, I’m initialising my repository instance variable and setting the map. Initially, I hide all the TextView.

在此onCreate()方法中,我将初始化存储库实例变量并设置地图。 最初,我隐藏了所有的TextView。

centerCamera(): (centerCamera():)

In this method, I’m getting LatLng as well as zoom value from the intent and setting the camera at the centre of the location.

在这种方法中,我从意图中获取LatLng以及缩放值,并将相机设置在位置的中心。

private fun centerCamera() {val latLng = intent.extras?.get(AppConstants.LAT_LNG) as LatLngval zoom = intent.extras?.get(AppConstants.ZOOM) as Floatmap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, zoom))
}

showReminderUpdate(): (showReminderUpdate():)

This method updates the reminder. On calling this method, it will show you circle around the location.

此方法更新提醒。 调用此方法后,它将向您显示该位置周围的圆圈。

private fun showReminderUpdate() {map.clear()showReminderInMap(this, map, reminder)
}

showConfigurationLocationStep(): (showConfigurationLocationStep():)

This method simply guides you through how you can navigate on the map.

此方法仅指导您完成如何在地图上导航的操作。

private fun showConfigureLocationStep() {
marker.visibility = View.VISIBLEinstructionTitle.visibility = View.VISIBLEradiusDescription.visibility = View.GONEmessage.visibility = View.GONEinstructionTitle.text = "Drag map to set location"next.setOnClickListener {
reminder
.latLng = map.cameraPosition.targetshowConfigureRadiusStep()}showReminderUpdate()
}

showConfigurationMessageStep(): (showConfigurationMessageStep():)

This method is same as the above method and it guides you through how to add a message. This message will be shown when you will get a notification.

此方法与上述方法相同,它会指导您完成添加消息的过程。 收到通知时将显示此消息。

private fun showConfigureMessageStep() {
marker.visibility = View.GONEinstructionTitle.visibility = View.VISIBLEmessage.visibility = View.VISIBLEinstructionTitle.text = "Enter a message"next.setOnClickListener {
reminder
.message = message.text.toString()if (reminder.message.isNullOrEmpty()) {
message.error = "Message Error"} else {
addReminder(reminder)
}}showReminderUpdate()
}

addReminder(): (addReminder():)

This method calls the add method from the MyReminderRepository class and adds the reminder. If the reminder is added successfully, then it will you Snackbar on the bottom with a message “Reminder added” else failure message.

此方法从MyReminderRepository类中调用add方法并添加提醒。 如果提示添加成功,那么它将在底部的Snackbar上显示一条消息“ Reminder included”,否则显示失败消息。

private fun addReminder(myReminder: MyReminder) {myRepository.add(myReminder,
success = {setResult(Activity.RESULT_OK)
finish()},
failure = {Snackbar.make(main, it, Snackbar.LENGTH_LONG).show()})
}

Finally modify your onMapReady()

最后修改您的onMapReady()

override fun onMapReady(googleMap: GoogleMap) {map = googleMapmap.uiSettings.isMapToolbarEnabled = falsecenterCamera()
showConfigureLocationStep()
}

Inside this method, we’re assigning the GoogleMap object to our map and then configuring settings.

在此方法内部,我们将GoogleMap对象分配给地图,然后配置设置。

Complete code for this activity

此活动的完整代码

Layout file for this activity

此活动的布局文件

The only thing you can notice in <fragment> tag is “android:name=”com.google.android.gms.maps.SupportMapFragment” line and this line helps you to add the Google map on the screen. This is the simplest way of adding a map. And rest of the code inside this file must be quite familiar for you.

您可以在<fragment>标记中唯一注意到的是“ android:name =” com.google.android.gms.maps.SupportMapFragment”行,该行可帮助您在屏幕上添加Google地图。 这是添加地图的最简单方法。 并且此文件中的其余代码对您来说必须非常熟悉。

MainActivity.kt: (MainActivity.kt:)

Finally, this is the last part of our code. This is our home activity which you will see when you first interact with the app.

最后,这是我们代码的最后一部分。 这是我们的家庭活动,当您首次与该应用进行交互时,您会看到它。

First of all, add these listeners in your activity and implement all its methods.

首先,在您的活动中添加这些侦听器并实现其所有方法。

class MainActivity : AppCompatActivity(), OnMapReadyCallback, GoogleMap.OnMarkerClickListener

Now create these instance variables.

现在创建这些实例变量。

lateinit var myRepository: MyReminderRepositoryprivate var map: GoogleMap? = null
private lateinit var locationManager
: LocationManager

onCreate(): (onCreate():)

In this method, we’re initialising our instance variables first, then getting a map from a fragment from the activity_main.xml file and the listeners on the action buttons. Initially, these buttons will not be visible until the user gives ACCESS_FINE_LOCATION permission. As a user grants the permission, we make the buttons visible to the users inside the onMapAndPermissionReady() method.

在这种方法中,我们首先初始化实例变量,然后从activity_main.xml文件和操作按钮上的侦听器的片段中获取映射。 最初,这些按钮在用户授予ACCESS_FINE_LOCATION权限后才可见 当用户授予权限时,我们使按钮对onMapAndPermissionReady()方法内的用户可见。

Look at the below code, here I’m attaching a listener to the button and inside the lambda expression, I’m calling run method of Google map class. This run is also a lambda function. When user presses (+) action button, we take the user to the ReminderActivity.kt class.

看下面的代码,在这里我在按钮上附加了一个侦听器,并且在lambda表达式中,我在调用Google map类的run方法。 此run也是lambda函数。 当用户按下(+)操作按钮时,我们会将用户带到ReminderActivity.kt类。

actionNewReminder.setOnClickListener {
map
?.run {
val
intent = AppConstants.newIntentForReminderActivity(this@MainActivity,cameraPosition.target,cameraPosition.zoom)
startActivityForResult(intent, AppConstants.REMINDER_REQUEST_CODE)}
}

onMapAndPermissionReady(): (onMapAndPermissionReady():)

When permission is granted and the map doesn’t equal null, then we’re making visible our buttons and adding a listener to the actionCurrentLocation button. Now if the user presses the actionCurrentLocation button, then we get the location provider and last known location of the user. And if this is not null, then we’ll move the camera to the last know location.

当授予权限且地图不等于null ,我们actionCurrentLocation按钮可见,并在actionCurrentLocation按钮上添加一个侦听器。 现在,如果用户按下actionCurrentLocation按钮,那么我们将获得位置提供者和用户的最后已知位置。 如果不为空,则将摄像机移至最后知道的位置。

onMarkerClick(): (onMarkerClick():)

When a user clicks on the marker, then an alert dialog pops up to ask user, if he wants to remove the reminder, if he presses “YES” then that reminder gets removed from the list.

当用户单击标记时,将弹出一个警告对话框,询问用户是否要删除提醒,如果他按“是”,则该提醒将从列表中删除。

override fun onMarkerClick(marker: Marker): Boolean {val reminder = myRepository.get(marker.tag as String)if (reminder != null) {
showReminderRemoveAlert(reminder)
}return true}

showReminderRemoveAlert():

showReminderRemoveAlert():

This method only creates an alert dialog with two options, i.e OK and Cancel and if a user clicks on “Ok”, then we call removeReminder() to remove it.

此方法仅创建一个带有两个选项的警报对话框,即“ 确定”和“ 取消” ,如果用户单击“确定”,则我们调用removeReminder()来将其删除。

private fun showReminderRemoveAlert(myReminder: MyReminder) {val alertDialog = AlertDialog.Builder(this).create()
alertDialog.run {setMessage("Reminder Removed")
setButton(
AlertDialog.BUTTON_POSITIVE,"OK") { dialog, _ ->removeReminder(myReminder)
dialog.dismiss()}setButton(
AlertDialog.BUTTON_NEGATIVE,"Cancel") { dialog, _ ->dialog.dismiss()}show()}}

Complete code of our ActivityMain.kt file

我们的ActivityMain.kt文件的完整代码

Now finally we have our activity_main.xml file

现在终于有了我们的activity_main.xml文件

运行并测试 (Run and Test)

  • Run the app and give permission.

    运行该应用程序并授予权限。
  • If you’re running app on emulator, then click on three dots you’ll see the follow screen. Now set any location you want (I set Connaught Place, New Delhi). And save this location.

    如果您在模拟器上运行应用程序,则单击三个点,您将看到以下屏幕。 现在设置您想要的任何位置(我设置为新德里的Connaught Place)。 并保存此位置。
Image for post
  • Now click on the first action button on your app and you’ll see your map will navigate to the specified location.

    现在,单击应用程序上的第一个操作按钮,您将看到地图将导航到指定位置。
  • Now add a reminder by click the (+) action button, drag the map to set your location, click on Continue and now type your message and again click Continue. Now your reminder has been saved.

    现在,通过单击(+)操作按钮添加提醒,拖动地图以设置您的位置,单击继续,现在键入您的消息,然后再次单击继续。 现在,您的提醒已保存。
Image for post
  • Now again click on three dots, change location to somewhere else on emulator and save it.

    现在再次单击三个点,将位置更改为模拟器上的其他位置并保存。
  • Click on the first button your app, you will reach to the location you set on your emulator.

    单击您的应用程序的第一个按钮,您将到达在模拟器上设置的位置。
  • Finally, In the emulator, set your location where you set your reminder while saving it. And wait for a few seconds you’ll a notification.

    最后,在模拟器中,设置保存提醒的位置。 然后等待几秒钟,您会收到通知。
Image for post

If you have any difficulty with your project, then you can seek help from my Github project.

如果您对项目有任何困难,可以从我的Github项目寻求帮助。

Subscribe to my mailing list to get the early access of my articles directly in your inbox and don’t forget to follow my own publication on Medium The Code Monster to polish your technical knowledge.

订阅我的邮件列表,直接在您的收件箱中获得我的文章的早期访问权限,不要忘记关注我在Medium The Code Monster上的出版物来完善您的技术知识。

结论 (Conclusion)

We’ve learnt how we can use Google map and Geofence APIs in our own project and we also learnt about services and broadcast receivers. Apart from this, we learnt a trick to store custom java object in SharedPreference using Gson and retrieving them using the same.

我们已经了解了如何在自己的项目中使用Google map和Geofence API,还了解了服务和广播接收器。 除此之外,我们还学习了一种技巧,该技巧将使用Gson将自定义Java对象存储在SharedPreference中,并使用相同的对象来检索它们。

翻译自: https://towardsdatascience.com/working-with-google-geofence-api-2e04a227ff27

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,作为一名Android开发人员,我可以为您实现这个需求。 首先,我们需要在AndroidManifest.xml文件中添加定位权限: ```xml <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> ``` 然后,我们需要创建一个Service来获取用户的位置信息。以下是一个简单的示例: ```java public class LocationService extends Service implements LocationListener { private LocationManager locationManager; private static final int MIN_TIME_BW_UPDATES = 1000 * 60 * 1; // 1 minute private static final int MIN_DISTANCE_CHANGE_FOR_UPDATES = 10; // 10 meters @Override public void onCreate() { super.onCreate(); locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return START_NOT_STICKY; } locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, MIN_TIME_BW_UPDATES, MIN_DISTANCE_CHANGE_FOR_UPDATES, this); return START_STICKY; } @Override public void onLocationChanged(Location location) { // do something with the location } @Override public void onProviderDisabled(String provider) { // handle provider disabled } @Override public void onProviderEnabled(String provider) { // handle provider enabled } @Override public void onStatusChanged(String provider, int status, Bundle extras) { // handle status changed } @Nullable @Override public IBinder onBind(Intent intent) { return null; } } ``` 这个Service使用GPS_PROVIDER获取用户的位置信息,并且在距离变化超过10米或者1分钟后更新位置信息。我们可以在onLocationChanged()方法中处理获取到的位置信息。 现在,我们需要在用户进入某个特定区域时发送通知。我们可以使用Geofencing API来实现这个功能。以下是一个简单的示例: ```java public class GeofenceHelper { private final Context context; public GeofenceHelper(Context context) { this.context = context; } public GeofencingRequest getGeofencingRequest(Geofence geofence) { return new GeofencingRequest.Builder() .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER) .addGeofence(geofence) .build(); } public Geofence getGeofence(LatLng latLng, float radius) { return new Geofence.Builder() .setRequestId("GEOFENCE_ID") .setCircularRegion(latLng.latitude, latLng.longitude, radius) .setExpirationDuration(Geofence.NEVER_EXPIRE) .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER) .build(); } public PendingIntent getGeofencePendingIntent() { Intent intent = new Intent(context, GeofenceBroadcastReceiver.class); return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } } ``` 这个GeofenceHelper类用于创建GeofencingRequest和Geofence,并且获取PendingIntent用于发送通知。我们需要在AndroidManifest.xml文件中添加广播接收器: ```xml <receiver android:name=".GeofenceBroadcastReceiver"/> ``` 然后,我们需要创建一个广播接收器来处理Geofence触发事件。以下是一个简单的示例: ```java public class GeofenceBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); if (geofencingEvent.hasError()) { return; } int geofenceTransition = geofencingEvent.getGeofenceTransition(); if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) { // send notification } } } ``` 这个广播接收器用于处理Geofence触发事件,并且发送通知。 最后,在我们的应用程序中启动LocationService和Geofencing服务: ```java LocationService locationService = new LocationService(); startService(new Intent(this, locationService.getClass())); GeofencingClient geofencingClient = LocationServices.getGeofencingClient(this); GeofenceHelper geofenceHelper = new GeofenceHelper(this); Geofence geofence = geofenceHelper.getGeofence(new LatLng(37.4220, -122.0841), 100); // create a geofence GeofencingRequest geofencingRequest = geofenceHelper.getGeofencingRequest(geofence); PendingIntent pendingIntent = geofenceHelper.getGeofencePendingIntent(); geofencingClient.addGeofences(geofencingRequest, pendingIntent); ``` 这样,我们就成功地实现了定位服务:使用Service可以在后台获取用户的位置信息,并且可以在用户进入某个特定区域时发送通知。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值