Updating the GPS position of a device in an Android app - android

I have a tiny Android app written in kotlin, it gets the location of the device. All the code is below. I am following this document https://developer.android.com/training/location/request-updates in order to implement updating the GPS position. But I only get the lastLocation once.
class MainActivity : AppCompatActivity() {
lateinit var locationRequest: LocationRequest
lateinit var locationCallback: LocationCallback
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
gpsPermissionCheck()
val client = LocationServices.getFusedLocationProviderClient(this)
client.lastLocation.addOnSuccessListener { location : Location? ->
location?.let {
println("We now have the location!")
println("Latitude:"+it.latitude.toString()+" Longitude:"+it.longitude. toString())
val compoID = resources.getIdentifier("txtlabel","id",packageName)
val theLabel = findViewById<TextView>(compoID)
theLabel.text = "Latit: "+it.latitude.toString()+" Longit:"+it.longitude. toString()
}
}
locationRequest = LocationRequest.create().apply {
interval = 10000
fastestInterval = 5000
//priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
locationResult ?: return
for (location in locationResult.locations){
// Update UI with location data
// ...
}
}
}
}
fun gpsPermissionCheck() {
try {
if (ContextCompat.checkSelfPermission(
applicationContext,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
101
)
} else {
//locationStart()
}
} catch (e: Exception) {
e.printStackTrace()
}
} /* End of gpsPermissionCheck */
}
When I add the block of code:
locationCallback = object : LocationCallback() {...}
I get this error message:
'onLocationResult' overrides nothing

Updated Answer
Step1:
Add manifest permissions
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
Step2:
Add your activity
companion object{
private const val UPDATE_INTERVAL_IN_MILLISECONDS = 10000L
private const val FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
UPDATE_INTERVAL_IN_MILLISECONDS / 2
}
private lateinit var mFusedLocationClient :FusedLocationProviderClient
init client onCreate function
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
Step3:
Create this callback on the top
private val mCallBack = object: LocationCallback(){
override fun onLocationResult(p0: LocationResult) {
Log.d(TAG, "onLocationResult: $p0")
super.onLocationResult(p0)
}
override fun onLocationAvailability(p0: LocationAvailability) {
Log.d(TAG, "onLocationAvailability: $p0")
super.onLocationAvailability(p0)
}
}
Step4:
Create location updates
private fun createLocationRequest() = LocationRequest.create().apply {
interval = UPDATE_INTERVAL_IN_MILLISECONDS
fastestInterval = FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS
priority = Priority.PRIORITY_HIGH_ACCURACY
}
#SuppressLint("MissingPermission")
fun requestLocationUpdates() {
try {
mFusedLocationClient.requestLocationUpdates(
createLocationRequest(),
mCallBack, Looper.myLooper()
)
} catch (ex: SecurityException) {
Log.e(TAG, "Lost location permission. Could not request updates. $ex")
}
}
Step5:
REQUEST RUN TIME PERMISSION
Permission succeed call this method
requestLocationUpdates()
Step6:
When you want to stop call this method
private fun removeLocationUpdates() {
try {
mFusedLocationClient.removeLocationUpdates(mCallBack)
} catch (ex : SecurityException) {
Log.e(TAG, "Lost location permission. Could not remove updates. $ex")
}
}

Related

Get user's current location in Jetpack Compose Android

Intention:
I am trying to get user's current location after user grants (coarse/fine)location permission. I am using jetpack compose accompanist lib to manager permission.
So when user grants the permission, am using getCurrentLocation of FusedLocationProviderClient to get the location object, and fetching lat lang from it.
Problem:
In the below code block, Logcat logs: Coordinates[0.0,0.0]
class LocationManager #Inject constructor(
private val fusedLocation: FusedLocationProviderClient
) {
#SuppressLint("MissingPermission")
#Composable
#ExperimentalPermissionsApi
fun getLatLang(context: Context): Coordinates {
val coordinates = Coordinates(0.0, 0.0)
/**
* Checking and requesting permission. If granted it will fetch current lat lang,
* else it will request for permission.
* If denied, will show popup to open app settings and grant location permission.
*/
LocationPermissionManager.RequestPermission(
actionPermissionGranted = {
fusedLocation.getCurrentLocation(LocationRequest.PRIORITY_HIGH_ACCURACY, null)
.addOnSuccessListener { location ->
if (location != null) {
coordinates.lat = location.latitude
coordinates.long = location.longitude
}
}
},
actionPermissionDenied = { context.openAppSystemSettings() }
)
return coordinates
}
}
data class Coordinates(var lat: Double, var long: Double)
Consuming LocationManager below:
#ExperimentalPermissionsApi
#Composable
fun AirQualityLayout(locationManager: LocationManager) {
val context: Context = LocalContext.current
val coordinates: Coordinates = locationManager.getLatLang(context = context)
if (coordinates.lat != 0.0 && coordinates.long != 0.0) {
Timber.d("Current location: $coordinates")
ShowUI()
}
}
Expecting suggestions/help what I am doing wrong here.
Do you have your manifest right? (Access_fine_location and access_coarse_location
This is a class i made sometime ago:
class LocationLiveData(var context: Context): LiveData<LocationDetails>() {
//add dependency implementation "com.google.android.gms:play-services-maps:18.0.2"
private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
override fun onActive() {
super.onActive()
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) { // alse geen permissie hebben just return, anders voer functie location uit
return
}
fusedLocationClient.lastLocation.addOnSuccessListener {
location -> location.also {
setLocationData(it)
}
}
}
internal fun startLocationUpdates() {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
}
private fun setLocationData(location: Location?) {
location?.let { it ->
//value is observed in LiveData
value = LocationDetails(
longitude = it.longitude.toString(),
lattitude = it.latitude.toString()
)
}
println("value $value")
}
override fun onInactive() {
super.onInactive()
fusedLocationClient.removeLocationUpdates(locationCallback)
}
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
println("we have a new location result")
locationResult ?: return //als er een result is dan prima, zo niet dan just return (elvis operator)
for (location in locationResult.locations) {
setLocationData(location = location)
}
}
}
companion object {
val ONE_MINUTE: Long = 1000
#RequiresApi(Build.VERSION_CODES.S)
val locationRequest : com.google.android.gms.location.LocationRequest = com.google.android.gms.location.LocationRequest.create().apply {
interval = ONE_MINUTE
fastestInterval = ONE_MINUTE/4
priority = LocationRequest.QUALITY_HIGH_ACCURACY
}
}
}
I've just resolved it with UiState, placing function into ViewModel class
Here my solution:
UiState:
data class MyCityUiState(
...
val currentLocation: Location? = null
...
)
update funtion in ViewModel:
fun updateCurrentLocation(location: Location?) {
_uiState.update {
it.copy(
currentLocation = location
)
}
}
fun that use ".addOnListener":
#SuppressLint("MissingPermission")
fun displayDistance(placeLocation: LatLng, context: Context): String? {
var fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
fusedLocationClient.lastLocation
.addOnSuccessListener { location: Location? ->
if (location != null) {
updateCurrentLocation(location)
}
}
var result: Float?
var formatResult: String? = null
val placeLocationToLocationType = Location("Place")
placeLocationToLocationType.latitude = placeLocation.latitude
placeLocationToLocationType.longitude = placeLocation.longitude
result =
uiState.value.currentLocation?.distanceTo(placeLocationToLocationType)
if (result != null) {
formatResult = "%.1f".format(result / 1000) + " km"
}
return formatResult
}
``

Use location on API GET call after requesting permissions

Essentially my problem is in the title, where I get permissions for location and from there try to get an API response based on the location. Problem is that the thread seems to continue before getting the answer? Here are the relevant snippets of code (and apologies for any beginner mistakes)
class ApiViewModel : ViewModel() {
private val _listOfRegions = MutableLiveData<List<Region>>()
val listOfRegions: LiveData<List<Region>> = _listOfRegions
fun getRegionsData(country: String) {
viewModelScope.launch {
Log.d("Workflow", "We will try to fetch info for $country")
try {
val listResult = SpotApi.retrofitService.getRegions(country)
Log.d("Workflow", listResult.toString())
if (listResult.isSuccessful) {
_listOfRegions.postValue(listResult.body())
Log.d("Workflow", listResult.body().toString())
} else {
_listOfRegions.value = emptyList()
}
} catch (e: Exception) {
Log.d("Workflow", "Failure: ${e.message}")
_listOfRegions.value = emptyList()
}
}
}
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
interface ApiService {
#GET("{country}")
suspend fun getRegions(#Path("country") country: String): Response<List<Region>>
}
object SpotApi {
val retrofitService: ApiService by lazy {
retrofit.create(ApiService::class.java)
}
}
object RegionsHelper {
fun getCurrentLocation(context: Context, lat: Double, long: Double): String {
val geocoder = Geocoder(context)
val locationResult = geocoder.getFromLocation(lat, long, 1)
val country = locationResult[0].countryCode
Log.d("Workflow", "Country is $country")
Log.d(
"Workflow",
locationResult[0].latitude.toString() + locationResult[0].longitude.toString()
)
return if (locationResult[0] != null) {
country
} else {
"Something Else"
}
}
}
class MainActivity : AppCompatActivity() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
val viewModel: ApiViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
// TODO: If LastLocation available use that to trigger result, else use default value
getLastLocation()
}
private fun getLastLocation() {
if (checkLocationPermission()) {
if (isLocationEnabled()) {
fusedLocationClient.lastLocation.addOnCompleteListener(this) { task ->
val location: Location? = task.result
if (location == null) {
requestNewLocationData()
} else {
Log.d("Workflow", "Permission Granted")
Log.d("Workflow", "Location is $location")
val retrievedCurrentCountry = getCurrentLocation(this#MainActivity, location.latitude, location.longitude)
Log.d("Workflow", "Current country is $retrievedCurrentCountry")
viewModel.getRegionsData(retrievedCurrentCountry)
//Log.d("Workflow", listOfRegions.toString())
}
}
}
}
}
}
Logs result are:
D/Workflow: Country is PT
D/Workflow: 38.7211345-9.139605
D/Workflow: Current country is PT
D/Workflow: We will try to fetch info for PT
D/Workflow: null
D/Workflow: Response{protocol=http/1.0, code=200, message=OK, url=http://192.168.1.181:5000/PT/}
D/Workflow: [Region(region=cascais, country=PT, long=-9.4207, lat=38.6968), Region(region=sintra, country=PT, long=-9.3817, lat=38.8029), Region(region=caparica, country=PT, long=-9.2334, lat=38.6446), Region(region=ericeira, country=PT, long=-9.4176, lat=38.9665)]*
Even though getting last location using fused location provider is very quick, sometimes it may take some time (upto 1000ms).
In your code, your API is called before getting last location. you can achieve this by using observer for your retrievedCurrentLocation.
class MainActivity : AppCompatActivity() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
val viewModel: ApiViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
// TODO: If LastLocation available use that to trigger result, else use default value
getLastLocation()
observeRetrievedLocation()
}
private fun observeRetrievedLocation() {
viewModel.retrievedLocation.observe(this) {
if (it.isNotEmpty) {
viewModel.getRegionsData(it)
}
}
}
private fun getLastLocation() {
if (checkLocationPermission()) {
if (isLocationEnabled()) {
fusedLocationClient.lastLocation.addOnCompleteListener(this) { task ->
val location: Location? = task.result
if (location == null) {
requestNewLocationData()
} else {
Log.d("Workflow", "Permission Granted")
Log.d("Workflow", "Location is $location")
val retrievedCurrentCountry = getCurrentLocation(this#MainActivity, location.latitude, location.longitude)
Log.d("Workflow", "Current country is $retrievedCurrentCountry")
viewModel.loadRetrievedLocation(retrievedCurrentCountry)
}
}
}
}
}
}
And your viewModel,
class ApiViewModel : ViewModel() {
private val _listOfRegions = MutableLiveData<List<Region>>()
val listOfRegions: LiveData<List<Region>> = _listOfRegions
private val _retrievedLocation = MutableLiveData<String>()
val retrievedLocation: LiveData<String> = _retrievedLocation
fun loadRetrievedLocation(retrievedLocation: String) {
_retrievedLocation.value = retrievedLocation
}
fun getRegionsData(country: String) {
viewModelScope.launch {
Log.d("Workflow", "We will try to fetch info for $country")
try {
val listResult = SpotApi.retrofitService.getRegions(country)
Log.d("Workflow", listResult.toString())
if (listResult.isSuccessful) {
_listOfRegions.postValue(listResult.body())
Log.d("Workflow", listResult.body().toString())
} else {
_listOfRegions.value = emptyList()
}
} catch (e: Exception) {
Log.d("Workflow", "Failure: ${e.message}")
_listOfRegions.value = emptyList()
}
}
}
so your API will be called only when the value of retrievedCurrentLocation Livedata changes and not null.

lateinit property mapRoute has not been initialized

I'm trying to make an app where it tracks the user and draws out the route the user has taken.
Whenever I try to install the app on the AVD the app keeps crashing and I get the error message that lateinit has not been initialized.
This is my code:
package com.example.map2020
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var map: GoogleMap
private lateinit var mapRoute: PolylineOptions
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var mCurrentLocation: Location
private lateinit var locationRequestTask: Task<LocationSettingsResponse>
private lateinit var locationCallback: LocationCallback
private lateinit var locationRequest: LocationRequest
private var requestingLocationUpdates = true
private val REQUESTING_LOCATION_UPDATES_KEY = "requesting-location-updates-key";
private val TAG = MapsActivity::class.java.simpleName
private val REQUEST_LOCATION_PERMISSION = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as? SupportMapFragment
mapFragment?.getMapAsync(this)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
locationRequest = LocationRequest.create()?.apply({
interval = 5000
fastestInterval = 3000
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
})!!
fusedLocationClient.lastLocation
.addOnSuccessListener { location : Location? ->
if (location != null) {
mCurrentLocation = location
}
}
locationRequestTask = createLocationRequest()
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
locationResult ?: return
for (location in locationResult.locations){
mapRoute.add(LatLng(location.latitude, location.longitude))
map.addPolyline(mapRoute)
}
}
}
}
override fun onMapReady(googleMap: GoogleMap) {
map = googleMap
val zoomLevel = 15f
val homeLatLng = LatLng(-6.240423, 106.605836)
map.moveCamera(CameraUpdateFactory.newLatLngZoom(homeLatLng, zoomLevel))
map.addMarker(MarkerOptions().position(homeLatLng))
mapRoute = PolylineOptions()
.width(8f)
.color(Color.GREEN)
map.addPolyline(mapRoute)
enableMyLocation()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.map_options, menu)
return true
}
// Checks that users have given permission
private fun isPermissionGranted() : Boolean {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
}
// Checks if users have given their location and sets location enabled if so.
private fun enableMyLocation() {
if (isPermissionGranted()) {
map.isMyLocationEnabled = true
}
else {
ActivityCompat.requestPermissions(
this,
arrayOf<String>(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_LOCATION_PERMISSION
)
}
}
// Callback for the result from requesting permissions.
// This method is invoked for every call on requestPermissions(android.app.Activity, String[],
// int).
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray) {
// Check if location permissions are granted and if so enable the
// location data layer.
if (requestCode == REQUEST_LOCATION_PERMISSION) {
if (grantResults.contains(PackageManager.PERMISSION_GRANTED)) {
enableMyLocation()
}
}
}
fun createLocationRequest(): Task<LocationSettingsResponse> {
val builder = locationRequest.let {
LocationSettingsRequest.Builder()
.addLocationRequest(it)
}
val client: SettingsClient = LocationServices.getSettingsClient(this)
return client.checkLocationSettings(builder.build())
}
override fun onResume() {
super.onResume()
if (requestingLocationUpdates) startLocationUpdates()
}
private fun startLocationUpdates() {
fusedLocationClient.requestLocationUpdates(locationRequest,
locationCallback,
Looper.getMainLooper())
}
override fun onPause() {
super.onPause()
stopLocationUpdates()
}
private fun stopLocationUpdates() {
fusedLocationClient.removeLocationUpdates(locationCallback)
}}
This is my error code:
kotlin.UninitializedPropertyAccessException: lateinit property mapRoute has not been initialized
at com.example.map2020.MapsActivity.access$getMapRoute$p(MapsActivity.kt:31)
at com.example.map2020.MapsActivity$onCreate$3.onLocationResult(MapsActivity.kt:73)
at com.google.android.gms.internal.location.zzau.notifyListener(Unknown Source)
at com.google.android.gms.common.api.internal.ListenerHolder.notifyListenerInternal(Unknown Source)
at com.google.android.gms.common.api.internal.ListenerHolder$zaa.handleMessage(Unknown Source)
at android.os.Handler.dispatchMessage(Handler.java:102)
at com.google.android.gms.internal.base.zap.dispatchMessage(Unknown Source)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Because callback locationCallback called before onMapReady. I recommend you startLocationUpdates() in onMapReady too. In onResume should check isMapReady too. You need flag to store map visible completely inside onMapReady

App crashes right after asking for location permission for the first time in frament

I am working in Kotlin and my fragment lies inside an activity. So when I run my app and as soon as the location fragment opens up it asks me for the permission but immediately crashes, so I'm unable to allow or deny the permission.
Here's my code for location fragment:
package com.example.atry.MakeComplaint
import android.app.Activity
import android.app.Activity.RESULT_OK
import com.example.atry.Retrofit.INodeJS
import com.example.atry.Retrofit.Observables
import com.example.atry.Retrofit.RetrofitClient
import android.content.Context
import android.content.IntentSender
import android.content.pm.PackageManager
import android.location.*
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Switch
import android.widget.Toast
import com.google.android.gms.maps.MapView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.atry.R
import com.google.android.gms.common.api.ResolvableApiException
import com.google.android.gms.location.*
import com.google.android.gms.maps.*
import com.google.android.gms.maps.model.*
import kotlinx.android.synthetic.main.fragment_location.view.*
import retrofit2.Call
import retrofit2.Response
class LocationFragment : Fragment(), OnMapReadyCallback, GoogleMap.OnMarkerClickListener {
override fun onMarkerClick(p0: Marker?)= false
private lateinit var map: GoogleMap
private lateinit var mapView : MapView
private lateinit var restrict:LatLngBounds
lateinit var myAPI: INodeJS
var MyCategory: Observables.Complainttype?=null
private var listener: OnLocationFragmentInteractionListener? = null
var makeComplaintobject1:Observables.final?=null
lateinit var typename:String
var objectComplaint =
Observables.Complaint(
1 , "dummy problem" ,
"url" ,
Observables.Location("78.4","17.4"),
Observables.ComplaintTypes("Smell" ),
Observables.Status( "Unresolved")
)
//for updating user's location/ for current location
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var locationRequest: LocationRequest
private lateinit var locationCallback: LocationCallback
// private lateinit var lastLocation: Location
var lastLocation:Location?=null
private var locationUpdateState = false
companion object {
private const val LOCATION_PERMISSION_REQUEST_CODE = 1
private const val REQUEST_CHECK_SETTINGS = 2 //For updating user's location as they move
}
fun sendCategoryItem(category: Observables.Complainttype) {
//receiving the category selected from Category Fragment
this.MyCategory = category
Log.d("here", "i am here 1")
Log.d("here", MyCategory.toString())
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
}
fusedLocationClient = LocationServices.getFusedLocationProviderClient(context!!)
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
locationResult ?: return
if (locationResult.locations.isNotEmpty()) {
// get latest location and sets it on the map
lastLocation = locationResult.lastLocation
Log.d("lastlocation", lastLocation.toString())
placeMarkerOnMap(LatLng(lastLocation!!.latitude, lastLocation!!.longitude))
} } }
getLocationUpdates()
//INIT API
val retrofit = RetrofitClient.instanc
myAPI = retrofit.create(INodeJS::class.java)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val v = inflater.inflate(R.layout.fragment_location, container, false)
mapView = v.findViewById(R.id.maps)
mapView.onCreate(savedInstanceState)
mapView.onResume()
try {
MapsInitializer.initialize(getActivity()!!.getApplicationContext())
} catch (sendEx: IntentSender.SendIntentException) {
sendEx.printStackTrace()
}
mapView.getMapAsync(this)
v.backToList.setOnClickListener {
backFragment()
}
v.forwardToDescription.setOnClickListener{
//will proceed to the Category Description fragment only if the lastLocation isn't null
getAllData()
}
return v
}
private fun setUpMap() {
if (activity!!.checkSelfPermission(
android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), LOCATION_PERMISSION_REQUEST_CODE)
return
}
}
private fun checkForPermissions(){
if(activity!!.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED){
requestPermissions( arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), LOCATION_PERMISSION_REQUEST_CODE)
}
startLocationUpdates()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if(grantResults.size >0){
if(requestCode== LOCATION_PERMISSION_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED){
locationUpdateState=true
checkForPermissions()
}
}
}
/**
* call this method in onCreate
* onLocationResult call when location is changed
*/
private fun getLocationUpdates() {
//with fusedLocationClient
// fusedLocationClient = LocationServices.getFusedLocationProviderClient(context!!)
locationRequest = LocationRequest()
locationRequest.interval = 1000
locationRequest.fastestInterval = 5000
locationRequest.smallestDisplacement = 170f // 170 m = 0.1 mile
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY //set according to your app function
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
val client = LocationServices.getSettingsClient(context!!)
val task = client.checkLocationSettings(builder.build())
//can update the map if location services on
task.addOnSuccessListener {
locationUpdateState=true
checkForPermissions()
}
task.addOnFailureListener { e ->
//check if the location settings is on yet
if(e is ResolvableApiException){
try{
e.startResolutionForResult(activity, REQUEST_CHECK_SETTINGS)
}catch (sendEx : IntentSender.SendIntentException){
}
}
}
}
//Places the marker on the map and changes its style.
private fun placeMarkerOnMap(location: LatLng) {
// 1
val markerOptions = MarkerOptions().position(location)
markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))
// 2
map.addMarker(markerOptions)
}
//start location updates
private fun startLocationUpdates() {
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
null /* Looper */
)
}
// stop location updates
private fun stopLocationUpdates() {
fusedLocationClient.removeLocationUpdates(locationCallback)
}
// start receiving location update when activity visible/foreground
override fun onResume() {
super.onResume()
mapView.onResume()
checkForPermissions()
// startLocationUpdates()
}
// stop receiving location update when activity not visible/foreground
override fun onPause() {
super.onPause()
mapView.onPause()
stopLocationUpdates()
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy()
}
override public fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
}
override fun onMapReady(googleMap: GoogleMap?) {
map = googleMap!!
map.uiSettings?.isZoomControlsEnabled = true
setUpMap()
map.isMyLocationEnabled = true
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
//updating the map with user's current location
if (location !=null){
lastLocation = location
val currentLatLng = LatLng(location.latitude,location.longitude)
placeMarkerOnMap(currentLatLng)
map.animateCamera(CameraUpdateFactory.newLatLngZoom(currentLatLng,12f))
}
}
}
private fun backFragment() {
val manager = (context as AppCompatActivity).supportFragmentManager
manager.popBackStackImmediate()
}
fun getAllData(){
//
// val latitude = 17.4
// val longitude = 78.4
// LocationUtils().getInstance(appContext)
// LocationUtils().getLocation().observe(this, Observer {loc: Location? ->
// location = loc!!
// // Yay! location recived. Do location related work here
// Log.i(TAG,"Location: ${location.latitude} ${location.longitude}")
//
// })
if(lastLocation!=null){
makeComplaintobject1 = Observables.final(
Observables.ComplaintTypes(MyCategory!!.typeName),
// Observables.Location(lastLocation.longitude.toString(),lastLocation.latitude.toString()) //lateinit wala
Observables.Location(lastLocation!!.longitude.toString(),lastLocation!!.latitude.toString())
)
}
else{
makeComplaintobject1 = Observables.final(
Observables.ComplaintTypes(MyCategory!!.typeName),
// Observables.Location(lastLocation.longitude.toString(),lastLocation.latitude.toString()) //lateinit wala
Observables.Location("","")
)
}
typename = MyCategory!!.typeName
val call = myAPI.checkExistingComplain(typename,makeComplaintobject1!!.finalLocation.longitude, makeComplaintobject1!!.finalLocation.latitude ) //new
Log.d("T", typename)
Log.d("Lo", makeComplaintobject1!!.finalLocation.longitude)
Log.d("La", makeComplaintobject1!!.finalLocation.latitude)
call.enqueue(object : retrofit2.Callback<Observables.checkExistingResult> {
override fun onFailure(call: Call<Observables.checkExistingResult>?, t: Throwable?) {
Log.d("NO", t!!.message)
}
override fun onResponse(call: Call<Observables.checkExistingResult>?, response: Response<Observables.checkExistingResult>?) {
Log.d("response popup", response!!.code().toString())
//
if(response.code() == 200){
Log.d("YES", response.code().toString())
Log.d("response", response.body().toString())
if(response.body()!!.Complain === null){
//if type and location are in db but does not match
Log.d("null",response.body()!!.Complain.toString())
var item1 = makeComplaintobject1
Log.d("wohoooooo",makeComplaintobject1.toString())
listener!!.onLocationFragmentInteraction1(item1!!) // typeName and location going to category description
}
else{
//if location or type matched
objectComplaint = response.body()!!.Complain!!
Log.d("got the complaint",objectComplaint.toString())
setExistingData(objectComplaint)
val item = objectComplaint
listener!!.onLocationFragmentInteraction(item) // all complaint going to popup
}
}
else if(response.code() == 500){
//if location or type is not in db
Log.d("response error", response.body().toString())
var item1 = makeComplaintobject1
Log.d("NOT IN DB",makeComplaintobject1.toString())
listener!!.onLocationFragmentInteraction1(item1!!) // typeName and location going to category description
}
else{
var item1 = makeComplaintobject1
Log.d("wohoooooo!1111",makeComplaintobject1.toString())
listener!!.onLocationFragmentInteraction1(item1!!) // typeName and location going to category description
//descriptionFragment()
}
}
})
}
interface OnLocationFragmentInteractionListener {
fun onLocationFragmentInteraction(item: Observables.Complaint?) // all complaint going to popup
fun onLocationFragmentInteraction1(item1: Observables.final) // typeName and location going to category description
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnLocationFragmentInteractionListener) {
listener = context
}
else {
throw RuntimeException("$context must implement OnLocationFragmentInteractionListener")
}
}
fun setExistingData(test: Observables.Complaint) {
objectComplaint = test
}
}
I have already asked for the ACCESS_FINE_LOCATION permission in the manifest and my min sdk version is 23 while targetsdk is 26.
After a lot of tries I am still getting the following error.
java.lang.SecurityException: my location requires permission ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION
What can I do to resolve this error?
If you are running on Marshmallow or greater version then you need to check permission of ACCESS_FINE_LOCATION if user has granted or not if not then you will get SecurityException when you try to access location without user consent. so before accessing location You check like this -
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
// you can access LOCATION
}
else
{
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 411);
}
}
else
{
// you can access LOCATION
}
Get Result of the Permission dialog,
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 411) {
if (grantResults.length == 0 || grantResults == null) {
// show dialog that you need access to go ahead
} else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Your code here permission granted
} else if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
// show dialog that you need access to go ahead
}
}
}
Now coming to your code
private fun checkForPermissions(){
if(activity!!.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED){
requestPermissions( arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), LOCATION_PERMISSION_REQUEST_CODE)
}
startLocationUpdates()
}
Right after
if(activity!!.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED)
this if condition end you are calling
startLocationUpdates()
so that's why after permission dialog system call this method and try to get location updates without user consent and app crash with SecurityException.
Your updates method could like below -
private fun checkForPermissions(){
if(activity!!.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED){
requestPermissions( arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), LOCATION_PERMISSION_REQUEST_CODE)
}else{
startLocationUpdates()
}
}
Hope this will help you.

How write unit test for my gps interactor class?

This class work fine and now i am interesting how write unit test
I have two independent flows:
location
gps status
I use publish subject for make my interactor in reactive manner.
Both flows mix with function Observable.withLatestFrom where i additionally add filtration and transformation.
Is it my gps class written correct ?
Can this class be tested ?
I need to test initFullLocationObservable
class GpsInteractor #Inject constructor(
private val locationManager: LocationManager,
private val googleApiClient: GoogleApiClient,
private val locationRequest: LocationRequest): GoogleApiClient.ConnectionCallbacks, LocationListener, GpsStatus.Listener {
private var gpsStatus: GpsStatus? = null
val locationSubject = PublishSubject.create<LocationModel>()
val satellitesSubject = PublishSubject.create<List<SatelliteModel>>()
lateinit var fullLocationObservable: Observable<FullLocation> private set
#SuppressLint("MissingPermission")
fun callLocationUpdates(updateInterval: Int, smallestDisplacement: Int, minAccuracy: Int, minSatellitesCount: Int, minSnr: Int) {
locationRequest.interval = (updateInterval * 1000).toLong()
locationRequest.fastestInterval = (updateInterval * 1000).toLong()
locationRequest.smallestDisplacement = smallestDisplacement.toFloat()
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
googleApiClient.registerConnectionCallbacks(this)
locationManager.addGpsStatusListener(this)
fullLocationObservable = initFullLocationObservable(minAccuracy, minSatellitesCount, minSnr)
googleApiClient.connect()
}
fun removeLocationUpdates() {
locationManager.removeGpsStatusListener(this)
if (googleApiClient.isConnected) {
LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, this)
googleApiClient.disconnect()
}
}
#SuppressLint("MissingPermission")
override fun onConnected(p0: Bundle?) { LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, this)
}
override fun onLocationChanged(location: Location) {
// TODO: make locationModel reusable
locationSubject.onNext(LocationModel(latitude = location.latitude, longitude = location.longitude,
time = location.time, speed = location.speed, accuracy = location.accuracy,
altitude = location.altitude, bearing = location.bearing))
}
#SuppressLint("MissingPermission")
override fun onGpsStatusChanged(event: Int) {
if (event != GpsStatus.GPS_EVENT_SATELLITE_STATUS) {
return
}
gpsStatus = locationManager.getGpsStatus(gpsStatus)
if (gpsStatus != null) {
// TODO: make satellitesModel reusable
val satellites: List<SatelliteModel> = gpsStatus!!
.satellites.filter { it.usedInFix() }
.map { SatelliteModel(it.prn, it.elevation, it.azimuth, it.snr) }
satellitesSubject.onNext(satellites)
}
}
private fun initFullLocationObservable(minAccuracy: Int, minSatellitesCount: Int, minSnr: Int): Observable<FullLocation> {
val locationObservable = locationSubject
.filter { locationModel -> locationModel.accuracy <= minAccuracy }
val satellitesObservable = satellitesSubject
.map { satellites: List<SatelliteModel> ->
satellites.filter { it.snr >= minSnr }
}
.filter { it.size >= minSatellitesCount }
return locationObservable.withLatestFrom(satellitesObservable, BiFunction { locationModel: LocationModel, satellitesModel: List<SatelliteModel> ->
val locationData = LocationData(locationModel)
val satellites = satellitesModel.map { Satellite(it.snr, locationData) }
FullLocation(locationData, satellites)
})
}
}

Categories

Resources