Saturday, December 10, 2022
HomeWeb DevelopmentThe way to construct a geocaching app with Android’s Fused Location

The way to construct a geocaching app with Android’s Fused Location


In response to the Oxford dictionary, geocaching refers to “an exercise or pastime during which an merchandise, or a container holding a number of objects, is hidden at a selected location for GPS customers to search out utilizing coordinates posted on the web.”

For a geocaching software, we would like the app to inform a consumer when they’re inside a particular radius of merchandise A. Let’s say that the consumer, represented by a marker, has an merchandise saved in a coordinate represented by one other marker. On this case, the marker for the merchandise is static, whereas the marker for the consumer is dynamic.

Utilizing the Fused Location library in Android, we will construct a geocaching software that gives a background service notification concerning the present consumer’s coordinates. The consumer will obtain a notification if they’re inside a five-mile radius of the cache and can proceed to be up to date a distance calculation in the event that they transfer nearer to or farther from the merchandise.

On the finish of the tutorial, our software will appear like this:

 

Geocaching App

To leap forward:

Stipulations

The reader is required to have the Android Studio code editor and Kotlin on their particular machine.

Getting began

We are going to begin by making a Google MapFragment. To take action, create a brand new Android Studio venture. Choose Google Maps Exercise as your template and fill in our app identify and bundle identify. Doing that takes a number of processes off the board as a result of now we solely have to get an API key from the Google Console:

Google MapFragment Process

New Project In Android Studio

Google Maps Activity Project

Subsequent, we are going to go to the Google developer’s console to get the API key.

Then, choose Create Credentials and API key to create an API key:

Copy our newly created key, head to the AndroidManifest.xml file, and paste it into the metadata tag attribute worth with the key phrase API key:

Paste New Key Into Metadata Tag

Creating the functionalities

After following the steps above, we solely have a customized Google Map created robotically by Android Studio. On this part, we need to use the fusedlcation API to get a steady location replace for the consumer, even after closing the applying. We’ll obtain this by way of a background notification replace.

To start, head over to the module construct.gradle file and add the dependencies under:

implementation 'com.google.android.gms:play-services-location:20.0.0'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"

Adding Dependencies To Build.Gradle File

Subsequent, head again to the AndroidManifest.xml file and set the next permissions, proper above the functions tag:

<uses-permission android:identify="android.permission.INTERNET" />
<uses-permission android:identify="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:identify="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:identify="android.permission.FOREGROUND_SERVICE" />

Creating abstractions and permission settings

We don’t need to open an software and robotically have location entry (effectively, functions don’t work like that). As an alternative, we would like the consumer to get a notification asking for entry to some system settings.

We’ll construct an interface file that abstracts the placement updates throughout the root folder and name it ClientInfo.kt. Inside the interface, we’ll create a operate with the parameter interval that specifies how typically we would like our location up to date. The operate will return a Move of sort Location from the coroutines library we added to the dependencies earlier.

We will even create a category to move a message in case our GPS is turned off:

interface ClientInfo {
    enjoyable getLocationUpdates(interval: Lengthy): Move<Location>

    class LocException(message: String): Exception()
}

Now, we have to present the implementation of the ClientInfo. So, throughout the identical root folder, create a category file known as DefaultClientInfo.kt, which can implement the interface (ClientInfo) we declared above. The category will then take two constructor parameters: Context and FusedLocationProviderClient.

Subsequent, we are going to override the getLocationUpdates operate, and utilizing the callbackFlow occasion, we’ll first examine if the consumer has accepted the placement permission. We’ll do that by making a utility file in the identical root folder known as ExtendContext.kt to write down an extension operate that returns a Boolean.

This operate will examine if the COARSE and FINE_LOCATION permissions are granted:

enjoyable Context.locationPermission(): Boolean{
    return  ContextCompat.checkSelfPermission(
        this,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )== PackageManager.PERMISSION_GRANTED &&
            ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED
}

If the consumer has allowed permission, we need to examine if they’ll fetch their location (if the placement is enabled) utilizing the SytemService LocationManager.

Now that we will fetch the consumer’s location, we have to create a request that may specify how typically we need to fetch the consumer’s location and the accuracy of the info. Additionally, we are going to create a callback that may use the onLocationResult operate each time the FusedLocationProviderClient fetches a brand new location.

Lastly, we are going to use the fusedlocation.requestLocationUpdates technique to name the callback operate, request, and a looper. Right here is the implementation:

class DefaultClientInfo(
    non-public val context:Context,
    non-public val fusedlocation: FusedLocationProviderClient
):ClientInfo{

    @SuppressLint("MissingPermission")
    override enjoyable getLocationUpdates(interval: Lengthy): Move<Location> {
        return callbackFlow {
            if(!context.locationPermission()){
                throw ClientInfo.LocException("Lacking Permission")
            }

            val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
            val hasGPS = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
            val hasNetwork = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
            if(!hasGPS && hasNetwork){
                throw  ClientInfo.LocException("GPS is unavailable")
            }

            val locationRequest = LocationRequest.create().apply {
                setInterval(interval)
                fastestInterval = interval
                precedence = Precedence.PRIORITY_HIGH_ACCURACY
            }
            val locationCallback = object : LocationCallback(){
                override enjoyable onLocationResult(outcome: LocationResult) {
                    tremendous.onLocationResult(outcome)
                    outcome.areas.lastOrNull()?.let{ location ->
                        launch { ship(location) }
                    }
                }
            }

            fusedlocation.requestLocationUpdates(
                locationRequest,
                locationCallback,
                Looper.getMainLooper()
            )

            awaitClose {
                fusedlocation.removeLocationUpdates(locationCallback)
            }
        }
    }
}

Making a foreground service

To create a foreground service, we are going to create one other class file in our root venture known as locservices.kt and make it inherit from the service class. Utilizing a coroutine, we are going to create a serviceScope that’s sure to the lifetime of the service, name the ClientInfo abstraction that we created earlier, and a category that shops the coordinate info of our cache.

class LocServices: Service(){

    non-public val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    non-public lateinit var clientInfo: ClientInfo

}

Subsequent, we are going to create an onBind operate that may return null as a result of we aren’t binding our service to something. Then, we are going to use the onCreate operate to name the DefaultClientInfo class, during which we are going to present applicationContext and
LocationServices.getFusedLocationProviderClient(applicationContext) because the parameters.

class LocServices: Service(){

    // do one thing

    override enjoyable onBind(p0: Intent?): IBinder? {
        return null
    }

    override enjoyable onCreate() {
        tremendous.onCreate()
        clientInfo = DefaultClientInfo(
            applicationContext,
            LocationServices.getFusedLocationProviderClient(applicationContext)
        )
    }
}

Now, we are going to create a companion object and, inside it, create a relentless worth START, which we ship to the service after we need to begin our monitoring. Then we are going to name the onStartCommand() operate for companies and supply the fixed we created earlier as an intent that we linked to a begin() operate:

class LocServices: Service(){

    override enjoyable onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when(intent?.motion){
            START -> begin()
        }
        return tremendous.onStartCommand(intent, flags, startId)
    }

    @SuppressLint("NewApi")
    non-public enjoyable begin(){
    }

    companion object{
        const val START = "Begin"
    }
}

The begin operate will deal with the notification to alert the consumer that their location is actively being monitored. Meaning the knowledge we need to present the consumer is the space (in meters) between them and the cache. To try this, we are going to use the Haversine formulation, which computes the space between two factors on a sphere utilizing their coordinates.

Thus, utilizing our callbackflow, we are going to name the clientInfo.getLocationUpdates(interval) technique, and utilizing the onEach technique offered by coroutines, we will get the up to date latitude and longitude.

As we stated earlier, we would like the consumer to know the space between them and the cache, however there’s a catch. We are not looking for the consumer to get a constant flurry of notifications telling them the space between them and the cache.

So, we are going to create a conditional assertion that checks if the consumer is inside a thousand-meter radius of the cache. If true, the consumer will get an ongoing notification informing them if they’re getting additional or nearer to the cache. As soon as they get inside a 50-meter radius, they’re notified with a unique message, and the service stops:

class LocServices: Service(){

    @SuppressLint("NewApi")
    non-public enjoyable begin(){
        val notif = NotificationCompat.Builder(this, "location")
            .setContentTitle("Geocaching")
            .setContentText("runnning within the background")
            .setSmallIcon(R.drawable.ic_launcher_background)
            .setOngoing(true)
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        clientInfo
            .getLocationUpdates(1000L)
            .catch { e -> e.printStackTrace() }
            .onEach { location ->
                val lat1 = location.latitude
                val long1 = location.longitude
                val radius = 6371 //in km
                val lat2 = secrets and techniques.d
                val long2 = secrets and techniques.d1
                val dlat = Math.toRadians(lat2 - lat1)
                val dlong = Math.toRadians(long2 - long1)
                val a = sin(dlat / 2) * sin(dlong / 2) + cos(Math.toRadians(lat1)) * cos(Math.toRadians(lat2)) * sin(dlong / 2) * sin(dlong / 2)
                val c = 2 * asin(sqrt(a))
                val valuresult = radius * c
                val km = valuresult / 1
                val meter = km * 1000
                val truemeter = String.format("%.2f", meter)
                if (meter > 100 && meter <= 1000){
                    val updatednotif = notif
                        .setContentText("You might be $truemeter meters away")
                    notificationManager.notify(1, updatednotif.construct())
                }
                if (meter < 100){
                    val getendnotice = notif
                        .setContentText("You might be $truemeter meters away, proceed together with your search")
                        .setOngoing(false)
                    notificationManager.notify(1, getendnotice.construct())
                    stopForeground(STOP_FOREGROUND_DETACH)
                    stopSelf()
                }
            }
            .launchIn(serviceScope)
        startForeground(1, notif.construct())
    }
}

Lastly, we are going to create an onDestroy operate that cancels the service after we shut the applying or clear our system cache. Right here is the implementation of the code under:

class LocServices: Service(){
    override enjoyable onDestroy() {
        tremendous.onDestroy()
        serviceScope.cancel()
    }
}

Now that we’ve the foreground service prepared, we are going to return to the AndroidManifest.xml file and the tag proper above the metadata tag:

<service android:identify=".fusedLocation.LocServices"
    android:foregroundServiceType = "location"/>

NotificationChannel

If we need to create a notification for our consumer’s distance to the cache, we have to create a channel to ship notifications. Let’s first create a category known as LocationApp.kt that we are going to make an Software().

Within the onCreate operate, we’ll create a notification channel from the Android oreo OS upwards. That is how the code seems to be:

class LocationApp: Software() {

    override enjoyable onCreate() {
        tremendous.onCreate()
        if (Construct.VERSION.SDK_INT >= Construct.VERSION_CODES.O) {
            val channel = NotificationChannel(
                "location",
                "Location",
                NotificationManager.IMPORTANCE_LOW
            )
            val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }
}

Lastly, we are going to add the attribute to the applying tag of the AndroidManifest.xml file under:

android:identify=".fusedLocation.LocationApp"

MapsActivity.kt

After we created a Google Maps Exercise, we bought a MapsActivity.kt file quite than the common MainActivity.kt. This file handles the creation of a map with a marker. We have to make a couple of modifications to that. So, let’s create three non-public lateinit variables: LocationCallback, LocationRequest and FusedLocationProviderClient.

Subsequent, we are going to create three features; launchintent, getupdatedlocation, and startupdate. We are going to name them within the onMapReady callback operate.

The launchintent operate handles the placement permission request, and the getupdatedlocation operate takes the LocationRequest and LocationCallback. The getupdatedlocation operate will even deal with calling the beginning operate utilizing its intent.

Lastly, throughout the startupdate operate, we are going to use the fusedlocation.requestLocationUpdates technique to name the callback operate, request, and a looper (set to null).


Extra nice articles from LogRocket:


Right here is how the code seems to be:

class MapsActivity : AppCompatActivity(), OnMapReadyCallback{

    companion object{
        non-public var firsttime = true
    }

    non-public lateinit var mMap: GoogleMap
    non-public lateinit var binding: ActivityMapsBinding
    non-public lateinit var locationCallback: LocationCallback
    non-public lateinit var locationRequest: LocationRequest
    non-public lateinit var fusedLocationProviderClient: FusedLocationProviderClient
    non-public var mMarker: Marker? = null
    non-public var secrets and techniques = Secretlocation()

    override enjoyable onCreate(savedInstanceState: Bundle?) {
        tremendous.onCreate(savedInstanceState)

        binding = ActivityMapsBinding.inflate(layoutInflater)
        setContentView(binding.root)
        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)

        // Receive the SupportMapFragment and get notified when the map is prepared for use.
        val mapFragment = supportFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)
    }

    non-public enjoyable launchintent() {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION
            ),
            0
        )
    }

    non-public enjoyable getupdatedlocation(){
        locationRequest = LocationRequest.create().apply {
            interval = 10000
            fastestInterval = 5000
            precedence = Precedence.PRIORITY_HIGH_ACCURACY
        }

        locationCallback = object : LocationCallback(){
            override enjoyable onLocationResult(outcome: LocationResult) {
                if (outcome.areas.isNotEmpty()){
                    val location = outcome.lastLocation
                    if (location != null){
                        mMarker?.take away()
                        val lat1 = location.latitude
                        val long1 = location.longitude
                        val d = secrets and techniques.d
                        val d1 = secrets and techniques.d1
                        val latlong = LatLng(lat1, long1)
                        val stuff = LatLng(d, d1)

                        val stuffoption= MarkerOptions().place(stuff).title("$stuff").icon(
                            BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE))
                        mMarker = mMap.addMarker(stuffoption)
                        val markerOptions = MarkerOptions().place(latlong).title("$latlong")
                        mMarker = mMap.addMarker(markerOptions)
                        if (firsttime){
                            mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latlong, 17f ))
                            firsttime = false
                        }
                    }
                }
            }
        }
        Intent(applicationContext, LocServices::class.java).apply {
            motion = LocServices.START
            startService(this)
        }
    }

    @SuppressLint("MissingPermission")
    non-public enjoyable startupdate(){
        fusedLocationProviderClient.requestLocationUpdates(
            locationRequest,
            locationCallback,
            null
        )
    }

    override enjoyable onMapReady(googleMap: GoogleMap) {
        mMap = googleMap
        launchintent()
        getupdatedlocation()
        startupdate()
        mMap.uiSettings.isZoomControlsEnabled = true
    }
}

After we run our software, we should always have the outcome under:

Final App Demo

Conclusion

On this tutorial, we created a map utilizing Android’s Fused Location library, which repeatedly updates the placement of a consumer on a map. We additionally created a foreground service figuring out the space between the consumer and a particular merchandise. Lastly, we created a notification for each time our consumer will get nearer to the cache.

Thanks for studying, and glad coding!

LogRocket: Immediately recreate points in your Android apps.

LogRocket is an Android monitoring resolution that helps you reproduce points immediately, prioritize bugs, and perceive efficiency in your Android apps.

LogRocket additionally helps you improve conversion charges and product utilization by displaying you precisely how customers are interacting together with your app. LogRocket’s product analytics options floor the the explanation why customers do not full a selected circulate or do not undertake a brand new function.

Begin proactively monitoring your Android apps — .

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments