I've got a very basic Android app in Kotlin.
I'm trying to center the map on the current location of the device, but myLocationOverlay.myLocation is always null.
Am I doing something wrong? I've been furiously googling but nothing seems to work.
Thanks!
class MainActivity : AppCompatActivity() {
private lateinit var map: MapView
private lateinit var mapController: IMapController
private lateinit var myLocationOverlay: MyLocationNewOverlay
override fun onCreate(savedInstanceState: Bundle?) {
// Load and set layout
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Create MapView
map = findViewById(R.id.map)
// Set tile source + display settings
map.setTileSource(TileSourceFactory.MAPNIK)
map.setMultiTouchControls(true)
map.zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
// Create MapController and set starting location
mapController = map.controller
// Create location overlay
myLocationOverlay = MyLocationNewOverlay(GpsMyLocationProvider(this), map)
myLocationOverlay.enableMyLocation()
myLocationOverlay.enableFollowLocation()
myLocationOverlay.isDrawAccuracyEnabled = true
myLocationOverlay.runOnFirstFix{runOnUiThread {
mapController.animateTo(myLocationOverlay.myLocation)
mapController.setZoom(9.5)
}}
map.overlays.add(myLocationOverlay)
// Set user agent
Configuration.getInstance().userAgentValue = "RossMaps"
println(myLocationOverlay.myLocation)
println("Create done")
}
override fun onResume() {
super.onResume()
map.onResume()
}
override fun onPause() {
super.onPause()
map.onPause()
}
}
So I found the solution. I needed to request the permission for ACCESS_FINE_LOCATION first. The following code works for me when added to the start of the onCreate function:
// Request Location permission
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PERMISSION_GRANTED) {
println("Location Permission GRANTED")
} else {
println("Location Permission DENIED")
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
1
)
}
A pop-up appears the first time you run the app in an AVD emulator asking for location permissions.
Related
To request the last known location of the user's device, we can use the fused location provider to retrieve the device's last known location using getLastLocation(), but using getCurrentLocation() gets a refresher, and more accurate location.
so, how to use the fusedLocationClient.getCurrentLocation() in Kotlin as there is no example illustrated in the documentation?
According to the documentation, the getCurrentLocation() takes two parameters.
The 1st parameter it takes is the priority (e.g. PRIORITY_HIGH_ACCURACY) to request the most accurate locations available, or any other priority that can be found here.
The 2nd parameter it takes is a cancellation token that can be used to cancel the current location request.
From the Google play services reference, a CancellationToken can only be created by creating a new instance of CancellationTokenSource.
so here is the code you need to use when using getCurrentLocation()
class YourActivity : AppCompatActivity() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.your_layout)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
fusedLocationClient.getCurrentLocation(LocationRequest.PRIORITY_HIGH_ACCURACY, object : CancellationToken() {
override fun onCanceledRequested(p0: OnTokenCanceledListener) = CancellationTokenSource().token
override fun isCancellationRequested() = false
})
.addOnSuccessListener { location: Location? ->
if (location == null)
Toast.makeText(this, "Cannot get location.", Toast.LENGTH_SHORT).show()
else {
val lat = location.latitude
val lon = location.longitude
}
}
}
}
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, object : CancellationToken() {
override fun onCanceledRequested(listener: OnTokenCanceledListener) = CancellationTokenSource().token
override fun isCancellationRequested() = false
})
.addOnSuccessListener {
if (it == null)
Toast.makeText(this, "Cannot get location.", Toast.LENGTH_SHORT).show()
else {
val lat = it.latitude
val lon = it.longitude
}
}
I know the questio is about Kotlin but I want to add for those searching for Java :
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, cancellationTokenSource.getToken())
.addOnSuccessListener(MyActivity.this, new OnSuccessListener<Location>() {
#Override
public void onSuccess(Location location) {
// Got last known location. In some rare situations this can be null.
if (location != null) {
//do your thing
}
Log.w(TAG, "No current location could be found");
}
});
I'm learning Kotlin, and I'm trying to make a simple app that displays the users long/lat. Everything seems like it should be working, but the location data keeps throwing the 'null else{}' case. Below is what my code looks like.
class MainActivity : AppCompatActivity() {
val RequestPermissionCode = 1
var mLocation: Location? = null
private lateinit var fusedLocationClient: FusedLocationProviderClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
getLastLocation()
}
fun getLastLocation(){
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermission()
}
else{
fusedLocationClient.lastLocation
.addOnSuccessListener {location: Location? ->
mLocation = location
if(location != null){
latitude.text = location.latitude.toString()
longitude.text = location.longitude.toString()
}
else{
latitude.text = "LOCATION_DENIED"
longitude.text = "LOCATION_DENIED"
}
}
}
}
private fun requestPermission(){
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), RequestPermissionCode)
this.recreate()
}
}
Any help would be greatly appreciated! I'm about to start pulling my hair out, and can't find the answers on Google ):
Emulators I have noticed have issues with Location data, plus lastLocation is not making a location request, so there is good chance there is no location data. What you need is requestLocationUpdate method, so that it can retrieve active location of the user. Also you will have better luck on a physical device for last Location.
I trying to implement google map with help of google compose sample project calls Crane in here:
https://github.com/android/compose-samples/tree/main/Crane
I went with same implementation and using MapViewUtils to implement lifeCycler for map and prevent re-compose stuff and more... I put all android map key and also permissions on manifest,
But my code getting crash on start of map:
This the point I wanna show map:
#Composable
fun MapScreen(latitude: String, longitude: String) {
// The MapView lifecycle is handled by this composable. As the MapView also needs to be updated
// with input from Compose UI, those updates are encapsulated into the MapViewContainer
// composable. In this way, when an update to the MapView happens, this composable won't
// recompose and the MapView won't need to be recreated.
val mapView = rememberMapViewWithLifecycle()
MapViewContainer(mapView, latitude, longitude)
}
#Composable
private fun MapViewContainer(
map: MapView,
latitude: String,
longitude: String
) {
// var zoom by savedInstanceState { InitialZoom }
AndroidView({ map }) { mapView ->
// Reading zoom so that AndroidView recomposes when it changes. The getMapAsync lambda
mapView.getMapAsync {
val position = LatLng(latitude.toDouble(), longitude.toDouble())
it.addMarker(
MarkerOptions().position(position)
)
it.moveCamera(CameraUpdateFactory.newLatLng(position))
}
}
}
And this is inside Util class:
#Composable
fun rememberMapViewWithLifecycle(): MapView {
val context = ContextAmbient.current
val mapView = remember {
MapView(context).apply {
id = R.id.map
}
}
// Makes MapView follow the lifecycle of this composable
val lifecycleObserver = rememberMapLifecycleObserver(mapView)
val lifecycle = LifecycleOwnerAmbient.current.lifecycle
onCommit(lifecycle) {
lifecycle.addObserver(lifecycleObserver)
onDispose {
lifecycle.removeObserver(lifecycleObserver)
}
}
return mapView
}
#Composable
private fun rememberMapLifecycleObserver(mapView: MapView): LifecycleEventObserver =
remember(mapView) {
LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_CREATE -> mapView.onCreate(Bundle()) //Crashes here
Lifecycle.Event.ON_START -> mapView.onStart()
Lifecycle.Event.ON_RESUME -> mapView.onResume()
Lifecycle.Event.ON_PAUSE -> mapView.onPause()
Lifecycle.Event.ON_STOP -> mapView.onStop()
Lifecycle.Event.ON_DESTROY -> mapView.onDestroy()
else -> throw IllegalStateException()
}
}
}
And I'm getting this crash:
2020-11-05 12:16:09.282 2665-3383/com.google.android.gms.persistent E/ModuleIdSetter: exception when setting module id
java.lang.IllegalStateException: Unable to get current module info in ModuleManager created with non-module Context
at com.google.android.chimera.config.ModuleManager.getCurrentModule(:com.google.android.gms#202414022#20.24.14 (040700-319035315):2)
at aewd.a(:com.google.android.gms#202414022#20.24.14 (040700-319035315):4)
at aewg.b(:com.google.android.gms#202414022#20.24.14 (040700-319035315):9)
at aeso.a(Unknown Source:0)
at rpm.a(:com.google.android.gms#202414022#20.24.14 (040700-319035315):0)
at rlv.c(:com.google.android.gms#202414022#20.24.14 (040700-319035315):1)
at rlt.b(:com.google.android.gms#202414022#20.24.14 (040700-319035315):1)
at rok.b(:com.google.android.gms#202414022#20.24.14 (040700-319035315):6)
at rok.c(:com.google.android.gms#202414022#20.24.14 (040700-319035315):6)
at rok.b(:com.google.android.gms#202414022#20.24.14 (040700-319035315):10)
at rok.a(:com.google.android.gms#202414022#20.24.14 (040700-319035315):17)
at rok.g(:com.google.android.gms#202414022#20.24.14 (040700-319035315):3)
at sdr.a(:com.google.android.gms#202414022#20.24.14 (040700-319035315):2)
at scr.a(:com.google.android.gms#202414022#20.24.14 (040700-319035315):10)
at sci.a(:com.google.android.gms#202414022#20.24.14 (040700-319035315):0)
at scl.handleMessage(:com.google.android.gms#202414022#20.24.14 (040700-319035315):28)
at android.os.Handler.dispatchMessage(Handler.java:107)
at aekz.a(:com.google.android.gms#202414022#20.24.14 (040700-319035315):2)
at aekz.dispatchMessage(:com.google.android.gms#202414022#20.24.14 (040700-319035315):14)
at android.os.Looper.loop(Looper.java:214)
at android.os.HandlerThread.run(HandlerThread.java:67)
You need to ask permission to access the user's location, and make sure you have it before showing the map. You can use a variable with LiveData and ViewModel that is updated on permission granted, here's a part of a example:
class MainViewModel : ViewModel() {
private val _permissionGranted = MutableLiveData(false)
val permissionGranted = _permissionGranted
fun onPermissionGranted() = _permissionGranted.postValue(true)
// ...
}
class MainActivity : AppCompatActivity() {
private val mainViewModel by viewModels<MainViewModel>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val permissionGranted = mainViewModel.permissionGranted.observeAsState()
if (permissionGranted) {
// logic to show your map
} else {
// logic to ask for permission
}
}
}
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>, grantResults: IntArray) {
// check if it's your request
mainViewModel.onPremissionGranted()
}
// ...
}
You can have more info on asking for permissions here: https://developer.android.com/training/permissions/requesting
Get rid of the return type from " fun rememberMapViewWithLifecycle(): MapView " to " fun rememberMapViewWithLifecycle() "
It's is clear that your composable function is returning a MapView, while it shouldn't return a type in order to be considered a composable function.
I quote from " https://developer.android.com/jetpack/compose/mental-model"
"""
The function doesn't return anything. Compose functions that emit UI do not need to return anything, because they describe the desired screen state instead of constructing UI widgets.
"""
I must have to take latitude and longitude of the user when user first time open an application.
So far, I have done as below :
Necessary Variables :
//Location Utils below :
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var lastLocation: Location
private lateinit var currentLatLng: LatLng
On Button Click : Checking the permission for the location, if has permission calling method named onLocationGranted() else asking for the location permission.
if (EasyPermissions.hasPermissions(
mContext,
FilePickerConst.PERMISSIONS_FINE_LOCATION
)
) {
onLocationGranted()
} else {
// Ask for one permission
EasyPermissions.requestPermissions(
this,
getString(R.string.permission_location),
Constant.MultiMediaRequestCode.LOCATION_PERMISSION,
FilePickerConst.PERMISSIONS_FINE_LOCATION
)
}
Below is the method onLocationGranted() : Here, Initializing LocationManager, Checking that GPS is on or not. If GPS on or enabled, taking location which is done in method named : getAndSaveCurrentLocation(). If, GPS is off or not enabled calling the method named buildAlertMessageNoGps()
val manager = mContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
//If GPS is not Enabled..
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
buildAlertMessageNoGps()
} else {
getAndSaveCurrentLocation()
}
Method getAndSaveCurrentLocation() is as below : In which I am taking location using fusedapi.
private fun getAndSaveCurrentLocation() {
try {
fusedLocationClient =
LocationServices.getFusedLocationProviderClient(mContext as AppBaseActivity)
fusedLocationClient.lastLocation.addOnSuccessListener(mContext as AppBaseActivity) { location ->
// Got last known location. In some rare situations this can be null.
if (location != null) {
lastLocation = location
currentLatLng = LatLng(location.latitude, location.longitude)
if (currentLatLng != null &&
currentLatLng.latitude != null &&
currentLatLng.longitude != null
) {
sharedPreferenceManager?.setStringData(
Constant.PrefKey.userLatitude,
"" + currentLatLng.latitude
)
sharedPreferenceManager?.setStringData(
Constant.PrefKey.userLongitude,
"" + currentLatLng.longitude
)
}
}
(mContext as AppBaseActivity).supportFragmentManager.popBackStack()
(activity as AppBaseActivity).addFragmentToRoot(
DashboardFragmentNew.newInstance(),
false
)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
Method buildAlertMessageNoGps() is as below : If GPS is off then, I am calling this method.
private fun buildAlertMessageNoGps() {
val builder = AlertDialog.Builder(mContext)
builder.setMessage("Your GPS seems to be disabled, do you want to enable it?")
.setCancelable(false)
.setPositiveButton("Yes", object : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, id: Int) {
startActivity(Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS))
}
})
.setNegativeButton("No", object : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, id: Int) {
dialog.cancel()
}
})
val alert = builder.create()
alert.show()
}
Now, the above method opens the settings to turn on GPS.
My question is : Suppose user turns on GPS, and coming back to screen, How can I get location then ?
Or If user not turning on GPS there, in that case How can I take location?
Thanks.
Suppose user turns on GPS, and coming back to screen, How can I get location then ?
You will get a call back to onActivityReenter similar to onActivityResult. So Override that method and call getAndSaveCurrentLocation() there.
In my application, I want to use mapBox for use map into my app and I want show current location when application loaded.
I write below codes, but when run application show me error in logcat (Not force close) and not load map!
My Activity codes:
class AddressMapPage : BaseActivity(), LocationEngineListener {
override var layoutId: Int = R.layout.activity_address_map_page
override var context: Context = this
private lateinit var mapirMap: MapirMap
private lateinit var latLng: LatLng
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Hide status bar
window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
(supportFragmentManager.findFragmentById(R.id.myMapView) as SupportMapFragment)
.getMapAsync { mapirMap ->
this#AddressMapPage.mapirMap = mapirMap
imageView2.setOnClickListener {
latLng = mapirMap.cameraPosition.target
toast(latLng.toString())
Log.e("TalAndLog", latLng.toString())
}
getLocationPermission()
}
}
private fun getLocationPermission() {
KotlinPermissions.with(this)
.permissions(Manifest.permission.ACCESS_FINE_LOCATION)
.onAccepted {
currentLocation()
}
.onDenied {
toast("Not allowed error message")
}
.onForeverDenied {
toast("Not allowed error message")
}
.ask()
}
private fun currentLocation() {
val component = mapirMap.locationComponent
component.activateLocationComponent(context)
component.isLocationComponentEnabled = true
if (component.locationEngine != null) {
component.locationEngine!!.addLocationEngineListener(this)
}
mapirMap.animateCamera(
CameraUpdateFactory
.newLatLngZoom(
LatLng(
component.lastKnownLocation!!.latitude,
component.lastKnownLocation!!.longitude
), 14.0
)
)
}
override fun onLocationChanged(location: Location?) {
if (location != null)
mapirMap.animateCamera(
CameraUpdateFactory.newLatLngZoom(
LatLng(
location.latitude,
location.longitude
), 14.0
)
)
}
override fun onConnected() {
}
}
Logcat errors:
kotlin.KotlinNullPointerException
at com.app.ui.address.map.AddressMapPage.currentLocation(AddressMapPage.kt:77)
at com.app.ui.address.map.AddressMapPage.access$currentLocation(AddressMapPage.kt:20)
at com.app.ui.address.map.AddressMapPage$getLocationPermission$1.invoke(AddressMapPage.kt:55)
at com.app.ui.address.map.AddressMapPage$getLocationPermission$1.invoke(AddressMapPage.kt:20)
at com.kotlinpermissions.KotlinPermissions$PermissionCore$onAccepted$1.onResult(KotlinPermissions.kt:56)
at com.kotlinpermissions.KotlinPermissions$PermissionCore.onReceivedPermissionResult$kotlin_permissions_release(KotlinPermissions.kt:36)
at com.kotlinpermissions.KotlinPermissions$PermissionCore.onAcceptedPermission(KotlinPermissions.kt:132)
at com.kotlinpermissions.KotlinPermissions$PermissionCore.ask(KotlinPermissions.kt:107)
at com.app.ui.address.map.AddressMapPage.getLocationPermission(AddressMapPage.kt:63)
at com.app.ui.address.map.AddressMapPage.access$getLocationPermission(AddressMapPage.kt:20)
at com.app.ui.address.map.AddressMapPage$onCreate$1.onMapReady(AddressMapPage.kt:47)
at ir.map.sdk_map.maps.SupportMapFragment.onMapReady(SupportMapFragment.java:124)
at ir.map.sdk_map.maps.MapView$MapCallback.onMapReady(MapView.java:1663)
at ir.map.sdk_map.maps.MapView$MapCallback.onDidFinishLoadingStyle(MapView.java:1701)
at ir.map.sdk_map.maps.MapChangeReceiver.onDidFinishLoadingStyle(MapChangeReceiver.java:195)
at ir.map.sdk_map.maps.NativeMapView.onDidFinishLoadingStyle(NativeMapView.java:995)
at android.os.MessageQueue.nativePollOnce(Native Method)
Show me error for this line : component.lastKnownLocation!!.latitude,
how can i fix it?
This example from the Mapbox documentation shows the recommended approach for showing a user's location on the map.
Since you are using Kotlin's not-null assertion operator (!!), a kotlin.KotlinNullPointerException is being thrown due to the fact that component.lastKnownLocation is null. This problem does not occur in the linked example because the enableLocationComponent method is called in the onMapReady callback. The enableLocationComponent method performs the necessary setup to use a LocationComponent instance with your Mapbox map.