FusedLocationProvider rapid updates when Google Maps are turned on - android

I'm using FusedLocationProvider in my app and I noticed that when my app is in the background and I start some other app that contains Google Map my original app starts receiving location updates extremely fast (like 1 update per second) despite setting up the fastest interval.
I know that I should unregister when going to background etc but this is not the case here.
Any ideas why this might happen or where I can report it to Google?
This is the activity I start it from (I've removed couple of permissions check just for the visibility)
The full repo can be found here
class MainActivity : AppCompatActivity() {
private val locationController by lazy { LocationController.getInstance(applicationContext) }
lateinit var button: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button = findViewById(R.id.button)
button.setOnClickListener {
if (locationController.isStarted) {
locationController.stop()
button.text = "START LOCATION UPDATES"
} else {
locationController.start()
button.text = "STOP LOCATION UPDATED"
}
}
}
And the LocationController looks like this:
class LocationController(context: Context) {
companion object {
#Volatile private var INSTANCE: LocationController? = null
fun getInstance(context: Context): LocationController {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: LocationController(context).also { INSTANCE = it }
}
}
}
private val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
private val locationRequest by lazy {
LocationRequest.create()
.setInterval(INTERVAL_MILLIS)
.setFastestInterval(FASTEST_INTERVAL_MILLIS)
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
}
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
Log.d("boom", "onLocationResult! ${locationResult.lastLocation}")
}
override fun onLocationAvailability(locationAvailability: LocationAvailability) {
super.onLocationAvailability(locationAvailability)
}
}
var isStarted: Boolean = false
#SuppressLint("MissingPermission")
fun start() {
fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
.addOnSuccessListener {
Log.d("boom", "requestLocationUpdates success!")
}
isStarted = true
}
fun stop() {
fusedLocationProviderClient.removeLocationUpdates(locationCallback)
.addOnSuccessListener {
Log.d("boom", "removeLocationUpdates success!")
}
isStarted = false
}
The constant values I experience it with are:
const val INTERVAL_MILLIS = 30_000L
const val FASTEST_INTERVAL_MILLIS = 10_000L

Related

Why I can only retrieve current location when already having permission?

I want to handle something in my ViewModel whenever the current location retrieved. But it didn't work at the first time I start the app and approve the permission. Only be able to see some logs after I close and start the app again.
init {
viewModelScope.launch {
locationRepository.location.collect {
Log.d(TAG, it.toString())
My repository to connect the location data source as you can see
class LocationRepositoryImpl #Inject constructor(
private val dataSource: LocationDataSource,
#ApplicationScope private val externalScope: CoroutineScope
) : LocationRepository {
override val location: Flow<MapLocation> = dataSource.locationSource
.shareIn(
scope = externalScope,
started = WhileSubscribed()
And the final is LocationDataSource where I put the logic to get the current location.
class LocationDataSource #Inject constructor(
private val client: FusedLocationProviderClient
) {
val locationSource: Flow<MapLocation> = callbackFlow {
val request = LocationRequest.create().apply {
interval = TimeUnit.SECONDS.toMillis(4)
fastestInterval = TimeUnit.SECONDS.toMillis(4)
priority = Priority.PRIORITY_HIGH_ACCURACY
}
val callBack = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
super.onLocationResult(result)
result.lastLocation?.let {
trySend(it.asModel())
}
}
}
//Subscribe to location changes.
client.requestLocationUpdates(request, callBack, Looper.getMainLooper())
awaitClose { client.removeLocationUpdates(callBack) }
The cause is I still did use the onPermissionResult() in my fragment, so after switch to the new requestPermissionLauncher = registerForActivityResult, and call onForegroundPermissionApproved() instead of in init of ViewModel, after approve the location permission. Everything work properly.

Showing MapView on Android Emulator

I'm trying to show a MapView in an emulator in Android Studios, but when I try to run the activity, the MapView won't show up. I have tried to install the right SDKs from the SDK Manager and think I have the correct emulator to run the MapView. Please let me know what else I can try to have the MapView work. Thank you!
Pixel 5
AVD Manager
SDK Manager
activity_business_location.xml
<com.google.android.gms.maps.MapView
android:id="#+id/businessLocationMapView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/toolbar"
android:contentDescription="#string/business_location_map"/>
BusinessLocationActivity.kt
class BusinessLocationActivity : AppCompatActivity() {
lateinit var jobService : JobService
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
private lateinit var locationRequest: LocationRequest
private lateinit var locationCallback: LocationCallback
private var currentLocation: Location? = null
private lateinit var placesService: PlacesService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_business_location)
jobService = intent.extras!!.get("jobService") as JobService
placesService = PlacesService.create()
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
setupLocationRequestAndCallback()
getLocationUpdates()
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.setTitle("Locate ${jobService.jobName}")
}
private fun setupLocationRequestAndCallback() {
locationRequest = LocationRequest.create().apply {
interval = 100
fastestInterval = 50
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
maxWaitTime= 10
}
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
currentLocation = locationResult.lastLocation
Log.e("Current Location", currentLocation.toString())
getBusinessesNearby()
// add this so location updates isn't running continuously
// only need to run once to get current users location
val removeTask = fusedLocationProviderClient.removeLocationUpdates(locationCallback)
removeTask.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d("BusinessLocation", "Location Callback removed.")
} else {
Log.d("BusinessLocation", "Failed to remove Location Callback.")
}
}
}
}
}
private fun getLocationUpdates() {
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.myLooper()!!)
}
fun getBusinessesNearby() {
val apiKey = getString(R.string.PLACES_API_KEY)
placesService.nearbyPlaces(
apiKey = apiKey,
location = "${currentLocation?.latitude},${currentLocation?.longitude}",
radiusInMeters = 80467,
keyword = jobService.jobName
).enqueue(
object : Callback<NearbyPlacesResponse> {
override fun onFailure(call: Call<NearbyPlacesResponse>, t: Throwable) {
Log.e("BusinessLocation", "Failed to get nearby places", t)
}
override fun onResponse(
call: Call<NearbyPlacesResponse>,
response: Response<NearbyPlacesResponse>
) {
if (!response.isSuccessful) {
Log.e("BusinessLocation", "Failed to get nearby places")
return
}
val places = response.body()?.results ?: emptyList()
Log.e("Places Count: ", places.size.toString())
for (place in places) {
Log.e("Place: ", place.toString())
}
}
}
)
}
}
NearbyPlacesResponse.kt
data class NearbyPlacesResponse(
#SerializedName("results") val results: List<Place>
)
PlacesService.kt
interface PlacesService {
#GET("nearbysearch/json")
fun nearbyPlaces(
#Query("key") apiKey: String,
#Query("location") location: String,
#Query("radius") radiusInMeters: Int,
#Query("keyword") keyword: String
): Call<NearbyPlacesResponse>
companion object {
private const val ROOT_URL = "https://maps.googleapis.com/maps/api/place/"
fun create(): PlacesService {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BODY
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
val converterFactory = GsonConverterFactory.create()
val retrofit = Retrofit.Builder()
.baseUrl(ROOT_URL)
.client(okHttpClient)
.addConverterFactory(converterFactory)
.build()
return retrofit.create(PlacesService::class.java)
}
}
}
Did you follow the documentation? I think it is well explained how to config it.
https://developers.google.com/maps/documentation/android-sdk/map
You need to add an Maps API Key to your project and do some other configs:
https://developers.google.com/maps/documentation/android-sdk/config
Here is how you create your Maps API Key:
https://developers.google.com/maps/documentation/android-sdk/get-api-key

Monitor if GPS enabled

I wish to add listener if GPS enabled or not.
This code determine if it enabled:
private fun locationEnabled(): Boolean {
val manager = requireActivity().getSystemService(Context.LOCATION_SERVICE) as LocationManager
return manager.isProviderEnabled(LocationManager.GPS_PROVIDER)
}
I wish to listen to this change, so I tried:
val mLocationManager = requireActivity().getSystemService(Context.LOCATION_SERVICE) as LocationManager
val mGnssStatusCallback = object : GnssStatus.Callback() {
override fun onStarted() {
super.onStarted()
Log.d(TAG,".onStarted()")
}
override fun onStopped() {
super.onStopped()
Log.d(TAG,".onStopped()")
}
}
var pop = mLocationManager.registerGnssStatusCallback(mGnssStatusCallback)
But its not giving the result I need, it not consistent with if it enabled/disabled.

Geofences events just called with fake gps app, in real gps it is not called

I am creating a foreground service that creates a geofence area in user local, and when the user exit from the geofence area i create another geofence area, again in user local.
I have a low RAM Quantity so i tested on my phone android 9, and with the fake gps app, it works fine, but when i use the real gps don't. I defined the high accuracy on android phone configuration and still doesn't work. Finally i had decided to put fire on my pc and use the emulator to control the phone location, and yet doesn't work. Not even de initial trigger is called.
So what is the difference between the fake gps and de real gps that defines if my code will work or not
class LocationForegroundService : LifecycleService() {
private lateinit var geofencingClient: GeofencingClient
private lateinit var locationClient: FusedLocationProviderClient
private lateinit var pendingBroadcastIntent: PendingIntent
private var geoId = ""
companion object {
private var count = 0
private var geoAlreadyInitialized = false
private const val TRACKING_CHANNEL = "tracking_channel"
private const val TRACKING_NOTIFICATION_ID = 1
private lateinit var mContext: Context
private val exitFromGeofence = MutableLiveData(false)
var isTrackingRider: Boolean = false
private set
fun startService(context: Context) {
mContext = context
val startIntent = Intent(context, LocationForegroundService::class.java)
ContextCompat.startForegroundService(context, startIntent)
isTrackingRider = true
}
fun stopService(context: Context) {
val stopIntent = Intent(context, LocationForegroundService::class.java)
context.stopService(stopIntent)
isTrackingRider = false
}
class GeofenceReceiver : BroadcastReceiver() {
#SuppressLint("MissingPermission")
override fun onReceive(context: Context?, intent: Intent?) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
return
}
when (geofencingEvent.geofenceTransition) {
GEOFENCE_TRANSITION_EXIT -> {
geofencingEvent.triggeringGeofences.forEach {
context?.showShortToast("EXIT geo ${it.requestId}")
}
exitFromGeofence.value = true
}
GEOFENCE_TRANSITION_ENTER -> {
geofencingEvent.triggeringGeofences.forEach {
context?.showShortToast("Enter geo ${it.requestId}")
}
}
GEOFENCE_TRANSITION_DWELL -> {
context?.showShortToast("dwell geo blabla")
}
}
}
}
}
override fun onCreate() {
super.onCreate()
locationClient = LocationServices.getFusedLocationProviderClient(mContext)
geofencingClient = LocationServices.getGeofencingClient(mContext)
val intent = Intent(mContext, GeofenceReceiver::class.java)
pendingBroadcastIntent = PendingIntent.getBroadcast(
mContext,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
createObservers()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
createNotificationChannel()
// ... creates notification for foreground
updateLocation()
return START_NOT_STICKY
}
#SuppressLint("MissingPermission")
private fun createNewGeofence(location: Location) {
removeActualGeofence()
geoId = "geo_id_$count"
val geofence = getGeofence(location)
val geofencingRequest = getGeofenceRequest(geofence)
initGeofence(geofencingRequest)
}
#SuppressLint("MissingPermission")
private fun initGeofence(geofencingRequest: GeofencingRequest) {
geofencingClient.addGeofences(geofencingRequest, pendingBroadcastIntent)
.addOnSuccessListener {
geoAlreadyInitialized = true
}
.addOnFailureListener {
}
}
#SuppressLint("MissingPermission")
private fun updateLocation() {
locationClient.lastLocation.addOnSuccessListener { location ->
createNewGeofence(location)
}
}
private fun getGeofenceRequest(geofence: Geofence) =
GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(geofence)
.build()
private fun getGeofence(location: Location) = Builder()
.setCircularRegion(location.latitude, location.longitude, 200F)
.setRequestId(geoId)
.setTransitionTypes(GEOFENCE_TRANSITION_ENTER or GEOFENCE_TRANSITION_EXIT)
.setExpirationDuration(NEVER_EXPIRE)
.setNotificationResponsiveness(0)
.build()
private fun removeActualGeofence() {
geofencingClient.removeGeofences(mutableListOf(geoId))
.addOnFailureListener {}
.addOnSuccessListener {
if (geoAlreadyInitialized) count++
}
}
private fun createObservers() {
exitFromGeofence.observe(this) {
updateLocation()
}
}
private fun createNotificationChannel() {
// ...create notification channel
}
}
The problem is that i was trying to use locationClient.lastLocation to get the user actual location, and when there was no last location to get it fail on create the geofence o the right place, just when i use the fake gps app the phone recorded a location and it works, so i changed to use requestLocationUpdates to get the actual locale.

ViewModel not updating with data change

So, this is my first time using architecture components in Android. I'm trying to create a ViewModel that will keep returning the latest location which can be used by UI elements. I've created a viewModel like this:
class LocationViewModel(application: Application) : AndroidViewModel(application) {
val currentLocation = MutableLiveData<Location?>()
init {
val ctx = getApplication<Application>().applicationContext
val fusedLocationProvider = LocationServices.getFusedLocationProviderClient(ctx)
val locationRequest = LocationRequest.create()
locationRequest.priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY
locationRequest.interval = 5000
locationRequest.fastestInterval = 2000
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
val client = LocationServices.getSettingsClient(ctx)
client.checkLocationSettings(builder.build()).addOnFailureListener {
currentLocation.postValue(null)
}
val locationCallback = object : LocationCallback() {
override fun onLocationResult(p0: LocationResult?) {
super.onLocationResult(p0)
p0 ?: return
currentLocation.postValue(p0.lastLocation)
}
}
fusedLocationProvider.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
}
}
And I observe this ViewModel in an activity, like so
class MainActivity : AppCompatActivity() {
private lateinit var locationText: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
locationText = findViewById(R.id.locationText)
val location = ViewModelProviders.of(this)[LocationViewModel::class.java]
location.currentLocation.observe(this, Observer { resutlLocation: Location? ->
locationText.text =
if (resutlLocation != null) "Lat: ${resutlLocation.latitude} Long: ${resutlLocation.longitude}" else "Null"
})
}
}
The TextView doesn't even gets updated once. How things like these should be done? What Am I doing wrong?
In View View model create function like this.
fun initdata() {
val ctx = getApplication<Application>().applicationContext
val fusedLocationProvider = LocationServices.getFusedLocationProviderClient(ctx)
val locationRequest = LocationRequest.create()
locationRequest.priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY
locationRequest.interval = 5000
locationRequest.fastestInterval = 2000
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
val client = LocationServices.getSettingsClient(ctx)
client.checkLocationSettings(builder.build()).addOnFailureListener {
currentLocation.postValue(null)
}
val locationCallback = object : LocationCallback() {
override fun onLocationResult(p0: LocationResult?) {
super.onLocationResult(p0)
p0 ?: return
currentLocation.postValue(p0.lastLocation)
}
}
fusedLocationProvider.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
}
then use like this
val viewModel = ViewModelProviders.of(this)[LocationViewModel::class.java]
viewModel.initdata()
Edit 1
You can lazy initialize like this way
private val users:MutableLiveData<Location?> by lazy {
MutableLiveData().also {
initdata()
}
}
more details refer ViewModel

Categories

Resources