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
}
``
Related
This code belongs to a fragment which shows the user's current location.After getting location when I want to go back to previous fragment App crashes.Logcat says error is happening here :
"val geocoder = Geocoder(requireContext(), Locale.getDefault())"
If anyone could assist me, I would greatly appreciate it
class LocationFragment : DialogFragment(), OnMapReadyCallback {
lateinit var binding: FragmentLocationBinding
private lateinit var map: GoogleMap
private val REQUEST_LOCATION_PERMISSION = 1
var lat: Double = 0.0
var long: Double = 0.0
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentLocationBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(requireContext())
enableMyLocation()
binding.apply {
prgBar.visibility=View.VISIBLE
btnSaveLocation.setOnClickListener {
dismiss()
}
val mapFragment = childFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this#LocationFragment)
}
}
override fun onStart() {
super.onStart()
val dialog: Dialog? = dialog
if (dialog != null) {
dialog.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
//dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
}
}
override fun onMapReady(p0: GoogleMap) {
map = p0
}
private fun isPermissionGranted(): Boolean {
return ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
#SuppressLint("VisibleForTests")
private fun checkDeviceLocationSettings(resolve: Boolean = true) {
val locationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_LOW_POWER
}
val requestBuilder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
val settingsClient = LocationServices.getSettingsClient(requireActivity())
val locationSettingsResponseTask =
settingsClient.checkLocationSettings(requestBuilder.build())
locationSettingsResponseTask.addOnFailureListener { exception ->
if (exception is ResolvableApiException && resolve) {
try {
exception.startResolutionForResult(
requireActivity(),
REQUEST_TURN_DEVICE_LOCATION_ON
)
} catch (sendEx: IntentSender.SendIntentException) {
Log.d(TAG, "Error getting location settings resolution: " + sendEx.message)
}
} else {
Snackbar.make(
binding.root,
R.string.location_required_error, Snackbar.LENGTH_INDEFINITE
).setAction(android.R.string.ok) {
checkDeviceLocationSettings()
}.show()
}
}
}
#TargetApi(Build.VERSION_CODES.Q)
private fun requestQPermission() {
val hasForegroundPermission = ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
if (hasForegroundPermission) {
val hasBackgroundPermission = ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.ACCESS_BACKGROUND_LOCATION
) == PackageManager.PERMISSION_GRANTED
if (hasBackgroundPermission) {
checkDeviceLocationSettings()
} else {
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
REQUEST_CODE_BACKGROUND
)
}
}
}
private fun enableMyLocation() {
if (isPermissionGranted()) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
checkDeviceLocationSettings()
} else {
requestQPermission()
}
updateLocation()
} else {
ActivityCompat.requestPermissions(
context as Activity,
arrayOf<String>(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_LOCATION_PERMISSION
)
}
}
private fun updateLocation() {
if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission
(requireContext(), Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED
) {
return
}
fusedLocationProviderClient.requestLocationUpdates(
locationRequest(), locationCallback,
Looper.myLooper()
)
}
private fun locationRequest(): LocationRequest {
return LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 3000)
.setWaitForAccurateLocation(false)
.setMinUpdateIntervalMillis(5000)
.setMaxUpdateDelayMillis(5000)
.build()
}
private var locationCallback = object : LocationCallback() {
override fun onLocationResult(p0: LocationResult) {
val location: Location? = p0.lastLocation
if (location != null) {
updateAddressUI(location)
}
}
}
#SuppressLint("MissingPermission")
fun updateAddressUI(location: Location) {
map.isMyLocationEnabled = true
val addressList: ArrayList<Address>
val geocoder = Geocoder(requireContext(), Locale.getDefault()) //Getting error here
addressList = geocoder.getFromLocation(
location.latitude,
location.longitude,
1
) as ArrayList<Address>
lat = addressList[0].latitude
long = addressList[0].longitude
binding.prgBar.visibility=View.INVISIBLE
Toast.makeText(requireContext(), "$lat \n $long", Toast.LENGTH_SHORT).show()
val latLng = LatLng(lat, long)
val markerOptions = MarkerOptions().position(latLng).title("I am here!")
map.animateCamera(CameraUpdateFactory.newLatLng(latLng))
map.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15f))
map.addMarker(markerOptions)?.setIcon(bitmapFromVector(requireContext(),R.drawable.baseline_emoji_people_24))
}
private fun bitmapFromVector(context: Context, vectorResId: Int): BitmapDescriptor? {
val vectorDrawable = ContextCompat.getDrawable(context, vectorResId)
vectorDrawable!!.setBounds(0, 0, vectorDrawable.intrinsicWidth, vectorDrawable.intrinsicHeight)
val bitmap = Bitmap.createBitmap(vectorDrawable.intrinsicWidth, vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
vectorDrawable.draw(canvas)
return BitmapDescriptorFactory.fromBitmap(bitmap)
}
override fun onResume() {
super.onResume()
enableMyLocation()
}
}
I used 'requireActivity' instead of 'requireContext' but didn't work
As you said, beacuse the crash happens when you go back to the previous fragment, probably the locationCallback getting called when the current fragment is detached from the activity. Inside the updateAddressUI you are getting the context in that momment when your view is detached.
If is ok with your logic you can keep the context reference inside the callBack object and work with this.
Try to change the locationCallback to and pass the context to the updateAddressUI function.
private var locationCallback = object : LocationCallback() {
val context = requireContext()
override fun onLocationResult(p0: LocationResult) {
val location: Location? = p0.lastLocation
if (location != null) {
updateAddressUI(location, context)
}
}
}
Change also the updateAddressUI function like this.
#SuppressLint("MissingPermission")
fun updateAddressUI(location: Location, context: Context) {
map.isMyLocationEnabled = true
val addressList: ArrayList<Address>
val geocoder = Geocoder(context, Locale.getDefault()) //Getting error here
addressList = geocoder.getFromLocation(
location.latitude,
location.longitude,
1
) as ArrayList<Address>
lat = addressList[0].latitude
long = addressList[0].longitude
binding.prgBar.visibility=View.INVISIBLE
Toast.makeText(context, "$lat \n $long", Toast.LENGTH_SHORT).show()
val latLng = LatLng(lat, long)
val markerOptions = MarkerOptions().position(latLng).title("I am here!")
map.animateCamera(CameraUpdateFactory.newLatLng(latLng))
map.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15f))
map.addMarker(markerOptions)?.setIcon(bitmapFromVector(context,R.drawable.baseline_emoji_people_24))
}
I changed the code in this way :
val geocoder = context?.let { Geocoder(it, Locale.getDefault()) }
in this methode :
fun updateAddressUI(location: Location) {
map.isMyLocationEnabled = true
val addressList: ArrayList<Address>
val geocoder = context?.let { Geocoder(it, Locale.getDefault()) }
if (geocoder != null) {
addressList = geocoder.getFromLocation(
location.latitude,
location.longitude,
1
) as ArrayList<Address>
lat = addressList[0].latitude
long = addressList[0].longitude
}
Iam retrieving the users location (longitude and lattitude). I made a class LocationLiveData, this is working fine.
What i want is to show the longitude in a TextField, so when the location of the user changes it has to update the TextField.
At this moment the textfield is only updating the longitude when i trigger a recompose of the composable.
It seems like there is going something wrong in the observeAsState()
The composable:
#Composable
fun HomeScreen(application:Application) {
//connect to viewModel
val viewModelLocations: ApplicationViewModel = ApplicationViewModel(application = application)
//observe the data in viewModel
val location by viewModelLocations.getLocationLiveData().observeAsState()
Column() {
OutlinedTextField(value = location.longitude, onValueChange = { location.longitude = it})
Text("The textfield has this text: " + location.longitude)
}
}
The ViewModel:
class ApplicationViewModel(application: Application): AndroidViewModel(application) {
private val locationLiveData = LocationLiveData(context = application)
fun getLocationLiveData(): LocationLiveData {
var data = locationLiveData
return data
}
//start the location updates
fun startLocationUpdates() {
locationLiveData.startLocationUpdates()
}
}
Class LocationLiveData (here iam retrieving the users location)
Note: if i print the .value its printing the userslocation longitude and lattitude
class LocationLiveData(var context: Context): MutableLiveData<LocationDetails>() {
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
) {
return
}
fusedLocationClient.lastLocation.addOnSuccessListener {
location -> location.also {
setLocationData(it)
}
}
}
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 (maak een functie, deze wordt alleen uitgvoerd indien locatie niet null is)
location?.let { it ->
//value is observed in LiveData
value = LocationDetails(
longitude = it.longitude.toString(),
lattitude = it.latitude.toString()
)
println("value $value")
}
}
//onInactive als de LiveData niet meer geobserveerd wordt
//we willen unsubscriben van de location updates (moet dus stoppen met updaten)
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 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")
}
}
I am developing a tracking application which tracks the path taken by user and plots a polyline on that
I am trying to add marker at certain position while tracking but the marker is not showing on the map
I have added the marker in onMapReady method but still it is not visible on the map
My source code
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private var lastKnownLocation: Location? = null
private var locationPermissionGranted: Boolean = false
private lateinit var placesClient: PlacesClient
private lateinit var mMap: GoogleMap
private lateinit var binding: ActivityMapsBinding
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
private val PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 121
private val REQUEST_CODE_ACTIVITY_RECOGNITION: Int = 0
val polylineOptions = PolylineOptions()
private val mapsActivityViewModel: MapsActivityViewModel by viewModels {
MapsActivityViewModelFactory(getTrackingRepository())
}
private fun getTrackingApplicationInstance() = application as TrackingApplication
private fun getTrackingRepository() = getTrackingApplicationInstance().trackingRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
Places.initialize(applicationContext, getString(R.string.maps_api_key))
placesClient = Places.createClient(this)
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
val isActivityRecognitionPermissionFree = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
val isActivityRecognitionPermissionGranted = EasyPermissions.hasPermissions(
this,
Manifest.permission.ACTIVITY_RECOGNITION
)
mapsActivityViewModel.lastTrackingEntity.observe(this) { lastTrackingEntity ->
lastTrackingEntity ?: return#observe
addLocationToRoute(lastTrackingEntity)
}
if (isActivityRecognitionPermissionFree || isActivityRecognitionPermissionGranted) {
setupLocationChangeListener()
} else {
EasyPermissions.requestPermissions(
host = this,
rationale = "For showing your step counts and calculate the average pace.",
requestCode = REQUEST_CODE_ACTIVITY_RECOGNITION,
perms = *arrayOf(Manifest.permission.ACTIVITY_RECOGNITION)
)
}
}
private fun addLocationToRoute(lastTrackingEntity: TrackingEntity) {
mMap.clear()
val newLatLng = lastTrackingEntity.asLatLng()
polylineOptions.points.add(newLatLng)
mMap.addPolyline(polylineOptions)
}
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
getLocationPermission()
updateLocationUI()
getDeviceLocation()
mMap.addMarker(
MarkerOptions().position(LatLng(16.667421, 74.819688))
.title("Marker in Sydney")
.icon(
BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)
)
)
}
val locationcallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
locationResult.locations.forEach {
val trackingEntity =
TrackingEntity(Calendar.getInstance().timeInMillis, it.latitude, it.longitude)
mapsActivityViewModel.insert(trackingEntity)
}
}
}
private fun setupLocationChangeListener() {
if (EasyPermissions.hasPermissions(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
val locationRequest = com.google.android.gms.location.LocationRequest()
locationRequest.priority =
com.google.android.gms.location.LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest.interval = 5000 // 5000ms (5s)
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
fusedLocationProviderClient.requestLocationUpdates(
locationRequest,
locationcallback,
Looper.getMainLooper()
)
} else {
getLocationPermission()
}
}
#SuppressLint("MissingPermission")
private fun updateLocationUI() {
try {
if (locationPermissionGranted) {
mMap.isMyLocationEnabled = true
mMap.uiSettings.isMyLocationButtonEnabled = true
} else {
mMap.isMyLocationEnabled = false
mMap.uiSettings.isMyLocationButtonEnabled = false
lastKnownLocation = null
getLocationPermission()
}
} catch (e: SecurityException) {
Log.e("Exception: %s", e.message, e)
}
}
#SuppressLint("MissingPermission")
private fun getDeviceLocation() {
try {
if (locationPermissionGranted) {
val locationResult = fusedLocationProviderClient.lastLocation
locationResult.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
lastKnownLocation = task.result
if (lastKnownLocation != null) {
mMap.moveCamera(
CameraUpdateFactory.newLatLngZoom(
LatLng(
lastKnownLocation!!.latitude,
lastKnownLocation!!.longitude
), 18.0F
)
)
}
} else {
val sydney = LatLng(-34.0, 151.0)
Log.d(TAG, "Current location is null. Using defaults.")
Log.e(TAG, "Exception: %s", task.exception)
mMap.moveCamera(
CameraUpdateFactory
.newLatLngZoom(sydney, 18.0F)
)
mMap.uiSettings.isMyLocationButtonEnabled = false
}
}
}
} catch (e: SecurityException) {
Log.e("Exception: %s", e.message, e)
}
}
private fun getLocationPermission() {
if (ContextCompat.checkSelfPermission(
this.applicationContext,
Manifest.permission.ACCESS_FINE_LOCATION
)
== PackageManager.PERMISSION_GRANTED
) {
locationPermissionGranted = true
} else {
ActivityCompat.requestPermissions(
this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
locationPermissionGranted = false
when (requestCode) {
PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION -> {
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED
) {
locationPermissionGranted = true
}
}
}
updateLocationUI()
}
}
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)
})
}
}