Blog Infos
Author
Published
Topics
,
Published
Topics
,
FusedLocationProviderClient

Photo by T.H. Chia on Unsplash

 

Recently Mapbox released its official compose extension just two months back. So I wanted to try it out though it doesn’t support all the existing Mapbox features out of the box in 0.1.0 the release, but I hope they will fix all the shortcomings soon.

[22 Oct 2023] There is presently an incompatibility between com.mapbox.maps:android:11.0.0-beta.1 any other dependencies of navigation etc. Due to multiple duplicate class errors in common block, the team is currently trying to resolve it here is the issue link.

Here is the repository if you want to take a look.

Let’s get Started

We will have a simple problem statement to display the mapboxmap, get user location permission with permission-flow-android and finally get the location with FusedLocationProviderClient.

On permission is given the Map transitions and adds a marker to the current location

Mapbox Setup With Compose Extension
  • Getting API Key: To get the Access tokens, first make an account in Mapbox. Make sure to check DOWNLOADS:READ. Copy this token, and add this to local.properties

  • Adding Dependencies: For adding dependency we have only two steps to consider, setting up the source to fetch dependencies with our mapbox api keysecondly, our Mapbox compose dependency, and finally the dependencies for getting user location.
/*
  File Name: settings.gradle.kts
*/

// Get the API key properties from local.properties
val keyProps = Properties().apply {
    file("local.properties").takeIf { it.exists() }?.inputStream()?.use { load(it) }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        // below code is added
        maven {
            url = uri("https://api.mapbox.com/downloads/v2/releases/maven")
            authentication {
                create<BasicAuthentication>("basic")
            }
            credentials {
                username = "mapbox"
                password = keyProps.getProperty("MAPBOX_MAP_TOKEN")
            }
        }
    }
}
/*
  File Name: build.gradle.kts(:app)
  add the compose extension with your other dependencies.
*/
dependencies {
  implementation("com.mapbox.extension:maps-compose:0.1.0")

  // Pick your versions of Android Mapbox Map SDK
  // Note that Compose extension is compatible with Maps SDK v11.0+.
  implementation("com.mapbox.maps:android:11.0.0-beta.1")
  
  // Handling Permission scenario
  implementation("dev.shreyaspatil.permission-flow:permission-flow-compose:1.2.0")
  
  // libs for fetching user current location and handling this Task API
  implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.0")
  implementation("com.google.android.gms:play-services-location:21.0.1")
}
Simple View with Marker

We will set the initial map style and the initial camera position by constructing the MapInitOptions using the context provided by the MapInitOptionsFactoryMapboxMap provides us will all the options to configure taps, compass settings and other interactive options. PointAnnotation is used to add markers on the map, we had added the marker image in the resource files, we have access to the onClick and other handle settings.
That’s it! It is that simple to render a map with Mapbox.✨

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Coroutines, Flow & Android- The Good Parts

In Kotlin Coroutines and Flow, there are many APIs and patterns that we have found either wrong or suboptimal in our work at Cash App and Square.
Watch Video

Coroutines, Flow & Android- The Good Parts

Bill Phillips & Stephen Edwards
Software Engineer & Android Engineer
Cash App & Square

Coroutines, Flow & Android- The Good Parts

Bill Phillips & St ...
Software Engineer & ...
Cash App & Square

Coroutines, Flow & Android- The Good Parts

Bill Phillips & ...
Software Engineer & Andro ...
Cash App & Square

Jobs

class MainActivity : ComponentActivity() {
    @OptIn(MapboxExperimental::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MapboxMap(
                modifier = Modifier.fillMaxSize(),
                mapInitOptionsFactory = { context ->
                    MapInitOptions(
                        context = context,
                        styleUri = Style.LIGHT,
                        cameraOptions = CameraOptions.Builder()
                            .center(Point.fromLngLat(24.9384, 60.1699))
                            .zoom(12.0)
                            .build()
                    )
                }
            ){
                AddPointer(Point.fromLngLat(24.9384, 60.1699))
            }
        }
    }

    @OptIn(MapboxExperimental::class)
    @Composable
    fun AddPointer(point:Point){
        val drawable = ResourcesCompat.getDrawable(
            resources,
            R.drawable.marker,
            null
        )
        val bitmap = drawable!!.toBitmap(
            drawable.intrinsicWidth,
            drawable.intrinsicHeight,
            Bitmap.Config.ARGB_8888
        )
        PointAnnotation(
            iconImageBitmap = bitmap,
            iconSize = 0.5,
            point = point,
            onClick = {
                Toast.makeText(
                    this,
                    "Clicked on Circle Annotation: $it",
                    Toast.LENGTH_SHORT
                ).show()
                true
            }
        )
    }
}
State Management & Current Location with Marker

Let’s start by making a LocationService which will provide us with the location of the user. It is quite simple FusedLocationProviderClient which is already an existing solution and a widely adapted approach to get device location.

The below implementation follows a pattern of throwing exceptions for permissions and GPS services. We got the FusedLoactionProviderClient and set up CurrentLocationRequestwith priority and accuracy. Finally, after the permission check, we make the call loactionProvider.getCurrentLoaction(request,null).await() to get the current user location.

This await() function comes from kotlinx-coroutines-play-services which is a library that integrates with the Google Play Services Tasks API. It includes extension functions like Task.asDeferred.

internal object LocationService {
    suspend fun getCurrentLocation(context: Context): Point {
        val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
        when {
            !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) -> throw LocationServiceException.LocationDisabledException()
            !locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) -> throw LocationServiceException.NoNetworkEnabledException()
            else -> {
                // Building FusedLocationProviderClient
                val locationProvider = LocationServices.getFusedLocationProviderClient(context)
                val request = CurrentLocationRequest.Builder()
                    .setPriority(Priority.PRIORITY_BALANCED_POWER_ACCURACY)
                    .build()

                runCatching {
                    val location = if (ActivityCompat.checkSelfPermission(
                            context,
                            Manifest.permission.ACCESS_FINE_LOCATION
                        ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
                            context,
                            Manifest.permission.ACCESS_COARSE_LOCATION
                        ) != PackageManager.PERMISSION_GRANTED
                    ) {
                        throw LocationServiceException.MissingPermissionException()
                    } else {
                        locationProvider.getCurrentLocation(request, null).await()
                    }
                    return Point.fromLngLat(location.longitude, location.latitude)
                }.getOrElse {
                    throw LocationServiceException.UnknownException(stace = it.stackTraceToString())
                }
            }
        }
    }

    sealed class LocationServiceException : Exception() {
        class MissingPermissionException : LocationServiceException()
        class LocationDisabledException : LocationServiceException()
        class NoNetworkEnabledException : LocationServiceException()
        class UnknownException(val stace: String) : LocationServiceException()
    }

}

Add permission in Manifest.xml

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

We will be making a list of all the required permissions list val permissionList = listOf(.....) to get the permission request we will be adding a button and an onClick listener. permissionLauncher.launch(permissionList.toTypedArray()). We will be listening to the permission state using rememberPermissionState(). In LaunchEffect we will add state as a dependency when the state changes, then check the state.isGranted or not. Finally, we will trigger the LoactionService to get the user’s location and update the currentLocation. This will eventually recompose the map add marker and redirect to the location on the map camera.

internal class MainActivity : ComponentActivity() {
    @OptIn(MapboxExperimental::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val permissionList = listOf(android.Manifest.permission.ACCESS_FINE_LOCATION)
        setContent {
            val coroutineScope = rememberCoroutineScope()
            val context = LocalContext.current
            val permissionLauncher = rememberPermissionFlowRequestLauncher()
            val state by rememberPermissionState(android.Manifest.permission.ACCESS_FINE_LOCATION)
            var currentLocation: Point? by remember { mutableStateOf(null) }
            val mapViewportState = rememberMapViewportState {
                setCameraOptions {
                    center(Point.fromLngLat(0.0, 0.0))
                    zoom(1.0)
                    pitch(0.0)
                }
            }

            LaunchedEffect(state){
                coroutineScope.launch {
                    if(state.isGranted) {
                        currentLocation = LocationService.getCurrentLocation(context)
                        val mapAnimationOptions =
                            MapAnimationOptions.Builder().duration(1500L).build()
                        mapViewportState.flyTo(
                            CameraOptions.Builder()
                                .center(currentLocation)
                                .zoom(12.0)
                                .build(),
                            mapAnimationOptions
                        )
                    }
                }
            }

            Column {
                if (state.isGranted) {
                    //TODO: adding search section
                } else {
                    Button(onClick = { permissionLauncher.launch(permissionList.toTypedArray()) }) {
                        Text("Request Permissions")
                    }
                }
                MainMapViewComposable(mapViewportState, currentLocation)
            }
        }
    }

    @Composable
    @OptIn(MapboxExperimental::class)
    private fun MainMapViewComposable(
        mapViewportState: MapViewportState,
        currentLocation: Point?
    ) {
        val gesturesSettings by remember {
            mutableStateOf(DefaultSettingsProvider.defaultGesturesSettings)
        }

        MapboxMap(
            modifier = Modifier.fillMaxSize(),
            mapViewportState = mapViewportState,
            gesturesSettings = gesturesSettings,
            mapInitOptionsFactory = { context ->
                MapInitOptions(
                    context = context,
                    styleUri = Style.TRAFFIC_DAY,
                    cameraOptions = CameraOptions.Builder()
                        .center(Point.fromLngLat(24.9384, 60.1699))
                        .zoom(12.0)
                        .build()
                )
            }
        ) {
            currentLocation?.let { AddSingleMarkerComposable(it, resources) }
        }
    }
}

That’s it! It is rendered a map with Mapbox ✨ adding a marker to the current location and handling location permission.

Reference Docs

 

 

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Compose is a relatively young technology for writing declarative UI. Many developers don’t even…
READ MORE
blog
When it comes to the contentDescription-attribute, I’ve noticed a couple of things Android devs…
READ MORE
blog
In this article we’ll go through how to own a legacy code that is…
READ MORE
blog
Compose is part of the Jetpack Library released by Android last spring. Create Android…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu