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)
})
}
}
Related
This is my location Client class
class DefaultLocationClient(
private val context: Context, private val client: FusedLocationProviderClient
) : LocationClient {
#SuppressLint("MissingPermission")
override fun getLocationUpdates(interval: Long): Flow<Location> {
return callbackFlow {
if (!context.hasLocationPermission()) {
throw LocationClient.LocationException("Missing location permission")
}
val locationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val isGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
val isNetWorkEnabled =
locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
if (!isGPSEnabled && !isNetWorkEnabled) {
throw LocationClient.LocationException("GPS is disabled")
}
// val request = LocationRequest.create().setInterval(interval).setFastestInterval(interval)
val request = LocationRequest.create().setPriority(Priority.PRIORITY_HIGH_ACCURACY).setInterval(interval).setFastestInterval(interval)
val locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
super.onLocationResult(result)
result.locations.lastOrNull()?.let { location ->
launch { send(location) }
}
}
}
client.requestLocationUpdates(request, locationCallback, Looper.getMainLooper())
awaitClose { client.removeLocationUpdates(locationCallback) }
}
}
}
This is LocationService class
class LocationService : Service() {
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val serviceScopeForRomm = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private lateinit var locationClient: LocationClient
private lateinit var bookDao: BookDao
lateinit var current: String
var strStatus: String = "Null";
lateinit var context: Context
override fun onBind(p0: Intent?): IBinder? {
Log.e("TAG", "Service Binding: ")
return null
}
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreate() {
super.onCreate()
context = applicationContext
locationClient = DefaultLocationClient(
applicationContext, LocationServices.getFusedLocationProviderClient(applicationContext)
)
val db = Room.databaseBuilder(
applicationContext, AppDatabase::class.java, "book_database"
).build()
bookDao = db.bookDao()
}
#RequiresApi(Build.VERSION_CODES.O)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_START -> start()
ACTION_STOP -> stop(true)
ACTION_RESTART -> stop(false)
}
return START_STICKY
}
#RequiresApi(Build.VERSION_CODES.O)
private fun start() {
startForegroundService()
}
#RequiresApi(Build.VERSION_CODES.O)
private fun startForegroundService() {
val notification =
NotificationCompat.Builder(this, "location").setContentText("Location:$strStatus")
.setContentTitle("Track-location-Test").setSmallIcon(R.mipmap.ic_launcher)
.setOngoing(true)
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
locationClient.getLocationUpdates(500L)
.catch { e -> Log.e("Tag11", "getLocationUpdates: ${e.message}") }.onEach { location ->
val formatter: DateTimeFormatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
current = LocalDateTime.now().format(formatter)
val lat = location.latitude.toString()
val lng = location.longitude.toString()
val updatedNotification = notification.setContentText("Location: ($lat,$lng)")
notificationManager.notify(1, updatedNotification.build())
Log.e("TAG", "$lat - $lng - $current")
insertData(lat, lng)
}.launchIn(serviceScope)
startForeground(1, notification.build())
}
var flagStopService: Boolean = false
private fun stop(isTrue: Boolean) {
flagStopService = isTrue
Log.e("tag111", "$isTrue stop: $flagStopService")
if (flagStopService) {
stopForeground(true)
stopSelf()
}
}
#RequiresApi(Build.VERSION_CODES.O)
fun insertData(lat: String, lng: String) {
//Insert
bookDao.insertBook(Book(0, lat, lng, current))
}
companion object {
const val ACTION_START = "ACTION_START"
const val ACTION_STOP = "ACTION_STOP"
const val ACTION_RESTART = "ACTION_RESTART"
}
#RequiresApi(Build.VERSION_CODES.O)
override fun onDestroy() {
serviceScope.cancel()
super.onDestroy()
}
}
Service and Client class are working fine as expected but once the device in on SleepMode or Ideal
val request = LocationRequest.create().setPriority(Priority.PRIORITY_HIGH_ACCURACY).setInterval(interval).setFastestInterval(interval)
is not working properly.
For e.g. if I set the interval=5000 Service return location after interval time is exceed (some time after 1 minutes)
I want to retrieve background location in every 5sec even when the device is in sleep mode.
In my kotlin project, google play console one error in Stability is - "android.os.NetworkOnMainThreadException". Please anyone help me to fix this issue. I have attached the screenshot of this issue and also i have added the signupactivity code i have used in my kotlin project.
class SignUpActivity : AppCompatActivity(), NetworkDialog.NetworkListener, GetResult.MyListener {
private lateinit var signUpViewModel: SignUpViewModel
private lateinit var locationViewModel: LocationViewModel
private var latitude: Double? = null
private var longitude: Double? = null
private var countryName: String? = null
private var countryCode: String? = null
private var state: String? = null
private var city: String? = null
private var nameUser: String? = null
private var email: String? = null
private var year: String? = null
private var month: String? = null
private var passwordUser: String? = null
private var isGPSEnabled = false
private lateinit var locationViewModelLatLong: Locationviewmodell
private lateinit var geoCoder: Geocoder
private val viewModel: MainViewModel by viewModels {
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T =
MainViewModel(
CoLocation.from(this#SignUpActivity),
CoGeocoder.from(this#SignUpActivity)
) as T
}
}
companion object {
const val requestShowSettings = 123
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sign_up)
date.listen()
DPref.init(this#SignUpActivity)
signUpViewModel = ViewModelProviders.of(this#SignUpActivity).get(SignUpViewModel::class.java)
locationViewModel = ViewModelProviders.of(this#SignUpActivity).get(LocationViewModel::class.java)
locationViewModelLatLong = ViewModelProviders.of(this#SignUpActivity).get(Locationviewmodell::class.java)
GpsUtils(this).turnGPSOn(object : GpsUtils.OnGpsListener {
override fun gpsStatus(isGPSEnable: Boolean) {
this#SignUpActivity.isGPSEnabled = isGPSEnable
}
})
signInButton.setOnClickListener {
onBackPressed()
}
date.minDate = dateMinMax(-99)
date.maxDate = dateMinMax(-18)
signUpButton.setOnClickListener {
try {
if (NetworkUtils.checkConnection()) {
invokeLocationAction()
isRegister()
} else {
val networkDialog = NetworkDialog()
networkDialog.showDialog(this#SignUpActivity)
networkDialog.setNetworkListener(this#SignUpActivity)
}
} catch (ex: Exception) {
ex.printStackTrace()
}
}
signUpViewModel.getUserSignUpData().observe(this) {
it.apply {
nameUser = userName
email = userEmail
year = userYear
month = userMonth
passwordUser = userPassword
}
}
locationViewModel.getUserSignUpLocationData().observe(this) {
it.apply {
latitude = userLatitude
longitude = userLongitude
countryName = userCountryName
countryCode = userCountryCode
state = userState
city = userCity
}
}
}
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null,
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
#Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
if (requestCode == GPS_REQUEST) {
isGPSEnabled = true
invokeLocationAction()
}
}
}
private fun invokeLocationAction() {
when {
!isGPSEnabled ->
Toast.makeText(this#SignUpActivity, "Please enable your location", Toast.LENGTH_SHORT).show()
isPermissionsGranted() -> startLocationUpdate()
shouldShowRequestPermissionRationale() ->
showSettingsDialog()
else -> ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION),
LOCATION_REQUEST
)
}
}
private fun startLocationUpdate() {
locationViewModelLatLong.getLocationData().observe(this, Observer {
val la = it.latitude
val lo = it.longitude
Log.d("SignUpActivity", "getLocationData() it.userLatitude$la")
Log.d("SignUpActivity", "getLocationData() it.userLatitude$lo")
getLocation(la,lo)
})
}
private fun isPermissionsGranted() = ActivityCompat.checkSelfPermission( this,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&this,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
private fun shouldShowRequestPermissionRationale() = ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.ACCESS_FINE_LOCATION) && ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.ACCESS_COARSE_LOCATION)
#SuppressLint("MissingPermission")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
LOCATION_REQUEST -> {
invokeLocationAction()
}
}
}
private fun getLocation(latitude: Double, longitude: Double){
try {
geoCoder = Geocoder(this#SignUpActivity, Locale.ENGLISH)
val addresses = geoCoder.getFromLocation(latitude, longitude, 1)
val countryName = addresses?.get(0)?.countryName
val countryCode = addresses?.get(0)?.countryCode
val state = addresses?.get(0)?.adminArea
val city = addresses?.get(0)?.locality
val locationData = LocationModelClass(
userLatitude = latitude,
userLongitude = longitude,
userCountryName = countryName,
userCountryCode = countryCode,
userState = state,
userCity = city
)
locationViewModel.setUserSignUpLocationData(locationModelClass = locationData)
} catch (e: IOException) {
e.printStackTrace()
}
}
}
const val LOCATION_REQUEST = 100
const val GPS_REQUEST = 101
view model codes:
class Locationviewmodell (application: Application) : AndroidViewModel(application) {
private val locationData = LocationLiveData(application)
fun getLocationData() = locationData
}
class LocationViewModel : ViewModel() {
private val userLocationData: MutableLiveData<LocationModelClass> =
MutableLiveData<LocationModelClass>()
fun setUserSignUpLocationData(locationModelClass: LocationModelClass) {
userLocationData.value = locationModelClass
}
fun getUserSignUpLocationData(): MutableLiveData<LocationModelClass> {
return userLocationData
}
}
class GpsUtils(private val context: Context) {
private val settingsClient: SettingsClient = LocationServices.getSettingsClient(context)
private val locationSettingsRequest: LocationSettingsRequest?
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
init {
val builder = LocationSettingsRequest.Builder()
.addLocationRequest(LocationLiveData.locationRequest)
locationSettingsRequest = builder.build()
builder.setAlwaysShow(true)
}
fun turnGPSOn(OnGpsListener: OnGpsListener?) {
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
OnGpsListener?.gpsStatus(true)
} else {
settingsClient
.checkLocationSettings(locationSettingsRequest)
.addOnSuccessListener(context as Activity) {
// GPS is already enable, callback GPS status through listener
OnGpsListener?.gpsStatus(true)
}
.addOnFailureListener(context) { e ->
when ((e as ApiException).statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED ->
try {
// Show the dialog by calling startResolutionForResult(), and check the
// result in onActivityResult().
val rae = e as ResolvableApiException
rae.startResolutionForResult(context, GPS_REQUEST)
} catch (sie: IntentSender.SendIntentException) {
}
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> {
val errorMessage =
"Location settings are inadequate, and cannot be " + "fixed here. Fix in Settings."
Log.e(TAG, errorMessage)
Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show()
}
}
}
}
}
interface OnGpsListener {
fun gpsStatus(isGPSEnable: Boolean)
}
}
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
}
``
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.
I want to when turn off the gps I see the settings dialog(requires) to turn of the gps Because my app needs GPS on. I have written code for this and put it in the onCreate() in MainActivity, but the dialog only shows me when the app runs, but I want to see this dialog wherever I turned off the GPS in app.
val settingsClient = LocationServices.getSettingsClient(this)
val locationRequest = LocationRequest()
val builder =
LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
.setAlwaysShow(false)
.setNeedBle(false)
settingsClient.checkLocationSettings(builder.build())
.addOnCompleteListener { task ->
if (task.isSuccessful) {
val response = task.result ?: return#addOnCompleteListener
val locationSettingsStates =
response.locationSettingsStates
Log.e("yyy", locationSettingsStates.toString())
// TODO
}
}
.addOnFailureListener { e ->
Timber.i("checkLocationSetting onFailure:" + e.message)
when ((e as ApiException).statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> {
Timber.i("Location settings are not satisfied. Attempting to upgrade " + "location settings ")
try {
// Show the dialog by calling startResolutionForResult(), and check the
// result in onActivityResult().
val rae = e as ResolvableApiException
rae.startResolutionForResult(this, 0)
} catch (sie: IntentSender.SendIntentException) {
Timber.i("PendingIntent unable to execute request.")
}
}
else -> {
}
}
}
}
It's a way to do this:
I created a LocationUtil class:
class LocationUtil(context: Context) {
companion object {
const val MIN_TIME: Long = 1000L
const val MIN_DISTANCE: Float = 0.0f
}
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private var locationListener: LocationListener? = null
val locationStateFlow = MutableStateFlow<Location>(Location(LocationManager.GPS_PROVIDER))
val gpsProviderState = mutableStateOf(false)
val isStart: MutableState<Boolean> = mutableStateOf(false)
private val locHandlerThread = HandlerThread("LocationUtil Thread")
init {
locHandlerThread.start()
}
#SuppressLint("MissingPermission")
fun start(minTimeMs: Long = MIN_TIME_MS, minDistanceM: Float = MIN_DISTANCE_M) {
locationListener().let {
locationListener = it
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minTimeMs, minDistanceM, it, locHandlerThread.looper)
}
gpsProviderState.value = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
isStart.value = true
}
fun stop() {
locationListener?.let {
locationManager.removeUpdates(it)
}
isStart.value = false
}
private fun locationListener() = object : LocationListener {
override fun onLocationChanged(location: Location) {
locationStateFlow.value = location
}
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
}
override fun onProviderEnabled(provider: String) {
gpsProviderState.value = true
}
override fun onProviderDisabled(provider: String) {
gpsProviderState.value = false
}
}
}
and I create a file with some functions:
private const val REQUEST_CODE_LOCATION_SOURCE_SETTINGS = 200
fun getLocationManager(context: Context): LocationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
fun isLocEnable(context: Context): Boolean {
val locationManager = getLocationManager(context)
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
}
#Composable
fun IsLocationEnable(context: Context) {
if (!isLocEnable(context)) {
SimpleAlertDialog(
title = stringResource(id = R.string.title),
text = stringResource(id = R.string.dialog_gps_setting),
singleButton = false,
confirmText = stringResource(R.string.settings),
dismissText = stringResource(R.string.cancel),
onConfirm = {
if (it) {
Intent(
Settings.ACTION_LOCATION_SOURCE_SETTINGS,
Uri.fromParts(
"package",
context.packageName,
null
)
).also { intent ->
try {
context.startActivity(
intent
)
} catch (e: ActivityNotFoundException) {
Intent(
Settings.ACTION_LOCATION_SOURCE_SETTINGS
).also { intentCatch ->
context.startActivity(
intentCatch
)
}
}
}
}
})
}
}
#Composable
fun LocationState(context: Activity) {
IsLocationEnable(context)
}
and finally I used codes in the MainActivity:
Box(modifier = Modifier.fillMaxSize()) {
Column(modifier = Modifier.fillMaxSize()) {
ConnectivityStatus()
ClientScaffold(clientNavigator)
}
val gpsEnable by locationUtil.gpsProviderState
if (!gpsEnable) {
IsLocationEnable(this#MainActivity)
}
}
How about having a Data layer LocationRepository with a gpsStatus flow like this
class LocationRepository(/** inject context **/) {
val gpsStatus = flow {
val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
while (currentCoroutineContext().isActive) {
emit(
manager.isProviderEnabled(LocationManager.GPS_PROVIDER)
)
delay(3000)
}
}
}
Then observe it at UI layer, to hide/ show the dialog.