how to create a service or process that updates location in background when application is in background? - android

i need a method to continuously get location updates in my application even when my application is in background. i have tried using Service and BroadcastReceiver but when application goes into background, location updates are stopped.
here is my Code with boradcast Receiver:-
MainActivity:
class MainActivity : AppCompatActivity() {
companion object {
private var instance: MainActivity? = null
fun getMainInstance(): MainActivity? {
return instance
}
}
private lateinit var client: FusedLocationProviderClient
private lateinit var request: LocationRequest
#RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
instance = this
client = LocationServices.getFusedLocationProviderClient(applicationContext)
request = LocationRequest.create()
request.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
request.interval = 1000
request.fastestInterval = 1000
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(
arrayOf(
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION
), 2201
)
} else {
client.requestLocationUpdates(request, getPendingIntent()!!)
}
}
override fun onPause() {
super.onPause()
Toast.makeText(applicationContext, "paused", Toast.LENGTH_SHORT).show()
}
override fun onStop() {
super.onStop()
Toast.makeText(applicationContext, "stopped", Toast.LENGTH_SHORT).show()
}
override fun onDestroy() {
super.onDestroy()
client.removeLocationUpdates(getPendingIntent()!!)
Toast.makeText(applicationContext, "destroyed", Toast.LENGTH_SHORT).show()
}
fun toastMessage(location: String) {
this.runOnUiThread {
Toast.makeText(
this,
location,
Toast.LENGTH_SHORT
).show()
}
}
private fun getPendingIntent(): PendingIntent? {
val intent = Intent(this,MyLocationReceiver::class.java)
intent.setAction(MyLocationReceiver.ACTION_PROCESS_UPDATE)
return PendingIntent.getBroadcast(this,0,intent,PendingIntent.FLAG_UPDATE_CURRENT)
}
}
BroadcastReceiver:
class MyLocationReceiver : BroadcastReceiver() {
companion object{
val ACTION_PROCESS_UPDATE = "com.example.backgroundlocation.UPDATE_LOCATION"
}
override fun onReceive(context: Context?, intent: Intent?) {
if(intent != null) {
val action = intent.action
if (action.equals(ACTION_PROCESS_UPDATE) && LocationResult.hasResult(intent)){
val locationResult = LocationResult.extractResult(intent)
try {
Log.d("onLocationReceived", locationResult.toString())
//MainActivity.getMainInstance()?.toastMessage(locationResult.toString())
} catch (e: Exception) {
Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show()
}
} else {
//MainActivity.getMainInstance()?.toastMessage("no result")
}
}
}
}
to me, the solution to this seems impossible.
Is there any way to update location when application is in background ? Kindly help me out!

To access the location while in the background you need to get this permission as well from the user in the Manifest
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
While giving the permission user needs to select allow all the time for location.

Related

FusedLocationProvider rapid updates when Google Maps are turned on

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

Unable to get data from Intent in Activity

I am very new to Kotlin and Android Development and really wanting to get into it. I am doing a project to help me understand and work with intent. The goal is simple, an android app that gets the users location in one activity, then passes it on to the next activity, where I intend to use it ( in the form of longitude and latitude). However I have been trying for hours to get the data to pass as an extra. The error that is show says nullpointer:
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Intent.getStringExtra(java.lang.String)' on a null object reference
at com.tba.mypoint_ofinterest.LocationInfo.<init>(LocationInfo.kt:11)
The two files relevant to this are:
AddLocation.kt - This is where I get the users location. Then the user presses a button: "btn_accept" and then I want it go to the LocationInfo.kt activity.
LocationInfo.kt-This is where I want to receive the location data and where the error message keeps getting thrown.
Here is the AddLocation.kt code:
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Looper
import android.view.View
import android.widget.Toast
import androidx.core.app.ActivityCompat
import com.google.android.gms.location.*
import kotlinx.android.synthetic.main.activity_add_location.*
class AddLocation : AppCompatActivity() {
//variables needed for location grab
lateinit var fusedLocationProviderClient: FusedLocationProviderClient
lateinit var locationRequest: LocationRequest
lateinit var locationCallback: LocationCallback
var REQUEST_CODE = 1000
lateinit var userLocation: Location
//Deal with permissions
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
REQUEST_CODE -> {
if (grantResults.size > 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
Toast.makeText(this, "Permission Granted", Toast.LENGTH_SHORT).show()
else
Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show()
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_location)
//Check for permission!
if (ActivityCompat.shouldShowRequestPermissionRationale(
this,
android.Manifest.permission.ACCESS_FINE_LOCATION
)
)
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_CODE
)
else {
buildLocationRequest()
buildLocationCallback()
//Create fused provider client
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
//get location
//start getting location
btnGetLocation.setOnClickListener(View.OnClickListener {
if (ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_CODE
)
return#OnClickListener
}
fusedLocationProviderClient.requestLocationUpdates(
locationRequest, locationCallback,
Looper.myLooper()
)
//make the button invisible after clicked
btnGetLocation.visibility = View.INVISIBLE
})
}
//Listen for clicking add location then turn off GPS and proceed to next view
btn_accept.setOnClickListener {
if (ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_CODE
)
}
fusedLocationProviderClient.removeLocationUpdates(locationCallback)
locationCallback = object : LocationCallback() {
override fun onLocationResult(p0: LocationResult?) {
userLocation = p0!!.locations.get(p0!!.locations.size - 1) //get last location
}
}
var long = userLocation.longitude
var lat = userLocation.latitude
addInfo(long,lat)
}
}
private fun buildLocationCallback() {
locationCallback = object : LocationCallback() {
override fun onLocationResult(p0: LocationResult?) {
var location = p0!!.locations.get(p0!!.locations.size - 1) //get last location
userLocation = location
txtLocation.text =
location.latitude.toString() + "/" + location.longitude.toString() + "and accuracy" + location.accuracy.toString()
}
}
}
private fun buildLocationRequest() {
locationRequest = LocationRequest()
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest.interval = 5000
locationRequest.fastestInterval = 3000
locationRequest.smallestDisplacement = 10f
}
//This gives intent and takes the GPS data to the next view to be combined with user input
fun addInfo(long:Double,lat:Double) {
val infoIntent: Intent = Intent(this, LocationInfo::class.java).apply {
putExtra("LAT_DATA", lat)
putExtra("LONG_DATA",long)
}
startActivity(infoIntent)
}
//Stop getting location data if back button is pressed
override fun onSupportNavigateUp(): Boolean {
fusedLocationProviderClient.removeLocationUpdates(locationCallback)
return super.onSupportNavigateUp()
}
}
Here is the LocationInfo.kt code:
import android.content.Intent
import android.location.Location
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import kotlinx.android.synthetic.main.activity_location_info.*
class LocationInfo : AppCompatActivity() {
val longitude = intent.getStringExtra("LONG_DATA")
val latitude = intent.getStringExtra("LAT_DATA")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_location_info)
btnSaveLocation.setOnClickListener{view ->saveInfo(view)}
textView2.text = longitude.toString()+"/"+latitude.toString()
}
fun saveInfo(x: View?){
val saveIntent: Intent = Intent(this,MainActivity::class.java)
startActivity(saveIntent)
}
}
I don't think I really understand how to use intent properly or I am mislabeling the loction data somewhere. Any advice is welcome, thank you
The issue is you are trying to access the intent even before the onCreate() is called, as a result intent is null.
Follow this,
class LocationInfo : AppCompatActivity() {
private lateinit var longitude:Double
private lateinit var latitude:Double
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_location_info)
btnSaveLocation.setOnClickListener{view ->saveInfo(view)}
longitude = intent.getDoubleExtra("LONG_DATA",0.0)
latitude = intent.getDoubleExtra("LAT_DATA",0.0)
textView2.text = longitude.toString()+"/"+latitude.toString()
}
fun saveInfo(x: View?){
val saveIntent: Intent = Intent(this,MainActivity::class.java)
startActivity(saveIntent)
}
}
You should access the Intent in the onCreate() method.
val longitude = intent.getDoubleExtra("LONG_DATA",0.0)
val latitude = intent.getDoubleExtra("LAT_DATA",0.0)

How to prevent memory leak with In-App Update Library

I want to implement the new In-App Update library in my app, but I've noticed that it trigger a memory leak in my activity when it's recreated/rotated.
Here's the only detail I have from LeakCanary:
Obviously, I've nothing if I remove the code from the In-App Update lib especially the addOnSuccessListener :
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){
updateInfo.value = appUpdateInfo
updateAvailable.value = true
}else{
updateInfo.value = null
updateAvailable.value = false
}
}
According to this post, I have first used some LiveData, but the problem was the same, so I used a full class to handle the callback, with LiveData :
My Service class :
class AppUpdateService {
val updateAvailable: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
val updateDownloaded: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
val updateInfo: MutableLiveData<AppUpdateInfo> by lazy { MutableLiveData<AppUpdateInfo>() }
fun checkForUpdate(appUpdateManager: AppUpdateManager){
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){
updateInfo.value = appUpdateInfo
updateAvailable.value = true
}else{
updateInfo.value = null
updateAvailable.value = false
}
}
}
fun checkUpdateOnResume(appUpdateManager: AppUpdateManager){
appUpdateManager.appUpdateInfo.addOnSuccessListener {
updateDownloaded.value = (it.installStatus() == InstallStatus.DOWNLOADED)
}
}
}
My Activity simplified :
class MainActivity : BaseActivity(), InstallStateUpdatedListener {
override fun contentViewID(): Int { return R.layout.activity_main }
private val UPDATE_REQUEST_CODE = 8000
private lateinit var appUpdateManager : AppUpdateManager
private val appUpdateService = AppUpdateService()
override fun onStateUpdate(state: InstallState?) {
if(state?.installStatus() == InstallStatus.DOWNLOADED){ notifyUser() }
}
// Called in the onCreate()
override fun setupView(){
appUpdateManager = AppUpdateManagerFactory.create(this)
appUpdateManager.registerListener(this)
setupAppUpdateServiceObservers()
// Check for Update
appUpdateService.checkForUpdate(appUpdateManager)
}
private fun setupAppUpdateServiceObservers(){
appUpdateService.updateAvailable.observe(this, Observer {
if (it)
requestUpdate(appUpdateService.updateInfo.value)
})
appUpdateService.updateDownloaded.observe(this, Observer {
if (it)
notifyUser()
})
}
private fun requestUpdate(appUpdateInfo: AppUpdateInfo?){
appUpdateManager.startUpdateFlowForResult(appUpdateInfo, AppUpdateType.FLEXIBLE, this, UPDATE_REQUEST_CODE)
}
private fun notifyUser(){
showSnackbar(getString(R.string.updated_downloaded), getString(R.string.restart)) {
appUpdateManager.completeUpdate()
appUpdateManager.unregisterListener(this)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == UPDATE_REQUEST_CODE) {
if (resultCode != RESULT_OK) {
Timber.d("Update flow failed! Result code: $resultCode")
}
}
}
override fun onDestroy() {
appUpdateManager.unregisterListener(this)
super.onDestroy()
}
override fun onResume() {
super.onResume()
appUpdateService.checkUpdateOnResume(appUpdateManager)
}
}
I don't really understand how to avoid the memory leak as the appUpdateManager has to be created with the context of the activity, and it looks to be the thing that causes the memory leak with the callback.
Does someone already implement it without having this issue?
Using weak reference to the context will probably solve your memory leak problem. Write this in your activity:
WeakReference<Context> contextWeakReference = new WeakReference<Context>(this);
Context context = contextWeakReference.get();
if (context != null) {
// Register using context here
}
There are lots of good articles on WeakReference, Garbage Collection and Memory Leaks to read more on the subject.
Also, onDestroy() is not guaranteed to be called. When you start another Activity, onPause() and onStop() method called instead of onDestroy().
The onDestroy() calls when you hit back button or call finish() method. So, unregister Listener in onPause() or onStop(). If you unregister in onDestroy() method, it might cause a memory leak.
Another idea is that since AppUpdateService class in not a subclass of ViewModel, it is not lifecycle aware. I'm not sure, but, you might need to remove observers in onstop/onDestroy of the activity and add them in onResume. (observers has a strong reference to the LifecycleOwner, here the activiy) To do that you need to define observers to be able to remove them later. Something like:
MutableLiveData<Boolean> someData = new MutableLiveData<>;
and then in onResume:
someData = appUpdateService.updateAvailable;
someData.observe()
and in onStop:
someData.removeObservers()
It's just a guess, but, I hope it would help somehow.
Thanks to #Sina Farahzadi I searched and try a lot of things and figured that the problem was the appUpdateManager.appUdateInfo call with the Task object.
The way I found to solve the memory leak is to use the applicationContext instead of the context of the activity. I'm not sure it's the best solution, but it's the one I've found for now. I've exported all in my service class so here's my code :
AppUpdateService.kt :
class AppUpdateService : InstallStateUpdatedListener {
val updateAvailable: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
val updateDownloaded: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
val notifyUser: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
val updateInfo: MutableLiveData<AppUpdateInfo> by lazy { MutableLiveData<AppUpdateInfo>() }
private var appUpdateManager : AppUpdateManager? = null
private var appUpdateInfoTask: Task<AppUpdateInfo>? = null
override fun onStateUpdate(state: InstallState?) {
notifyUser.value = (state?.installStatus() == InstallStatus.DOWNLOADED)
}
fun setupAppUpdateManager(context: Context){
appUpdateManager = AppUpdateManagerFactory.create(context)
appUpdateManager?.registerListener(this)
checkForUpdate()
}
fun onStopCalled(){
appUpdateManager?.unregisterListener(this)
appUpdateInfoTask = null
appUpdateManager = null
}
fun checkForUpdate(){
appUpdateInfoTask = appUpdateManager?.appUpdateInfo
appUpdateInfoTask?.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){
updateInfo.value = appUpdateInfo
updateAvailable.value = true
}else{
updateInfo.value = null
updateAvailable.value = false
}
}
}
fun startUpdate(activity: Activity, code: Int){
appUpdateManager?.startUpdateFlowForResult(updateInfo.value, AppUpdateType.FLEXIBLE, activity, code)
}
fun updateComplete(){
appUpdateManager?.completeUpdate()
appUpdateManager?.unregisterListener(this)
}
fun checkUpdateOnResume(){
appUpdateManager?.appUpdateInfo?.addOnSuccessListener {
updateDownloaded.value = (it.installStatus() == InstallStatus.DOWNLOADED)
}
}
}
MainActivity simplified :
class MainActivity : BaseActivity(){
override fun contentViewID(): Int { return R.layout.activity_main }
private val UPDATE_REQUEST_CODE = 8000
private var appUpdateService: AppUpdateService? = AppUpdateService()
/**
* Setup the view of the activity (navigation and menus)
*/
override fun setupView(){
val contextWeakReference = WeakReference<Context>(applicationContext)
contextWeakReference.get()?.let {weakContext ->
appUpdateService?.setupAppUpdateManager(weakContext)
}
}
private fun setupAppUpdateServiceObservers(){
appUpdateService?.updateAvailable?.observe(this, Observer {
if (it)
requestUpdate()
})
appUpdateService?.updateDownloaded?.observe(this, Observer {
if (it)
notifyUser()
})
appUpdateService?.notifyUser?.observe(this, Observer {
if (it)
notifyUser()
})
}
private fun removeAppUpdateServiceObservers(){
appUpdateService?.updateAvailable?.removeObservers(this)
appUpdateService?.updateDownloaded?.removeObservers(this)
appUpdateService?.notifyUser?.removeObservers(this)
}
private fun requestUpdate(){
appUpdateService?.startUpdate(this, UPDATE_REQUEST_CODE)
}
private fun notifyUser(){
showSnackbar(getString(R.string.updated_downloaded), getString(R.string.restart)) {
appUpdateService?.updateComplete()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == UPDATE_REQUEST_CODE) {
if (resultCode != RESULT_OK) {
Timber.d("Update flow failed! Result code: $resultCode")
}
}
}
override fun onStop() {
appUpdateService?.onStopCalled()
removeAppUpdateServiceObservers()
appUpdateService = null
super.onStop()
}
override fun onResume() {
super.onResume()
setupAppUpdateServiceObservers()
appUpdateService?.checkUpdateOnResume()
}
}
For now, I will keep it that way and continue to search for another way to do it.
Let me know if someone has a better way to do it.
Use this helper class:
class GoogleUpdater(activity: FragmentActivity) : LifecycleObserver {
private val appUpdateManager = AppUpdateManagerFactory.create(activity)
private var installStateUpdatedListener: InstallStateUpdatedListener? = null
private var wra = WeakReference(activity)
private val activity get() = wra.get()
init {
activity.lifecycle.addObserver(this)
}
fun checkUpdate() {
fun showCompleteUpdateDialog() {
activity?.let { activity ->
if (!activity.isFinishing)
AlertDialog.Builder(activity)
.setTitle(R.string.notification)
.setMessage(R.string.restart_to_complete_update)
.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_notification)
?.apply {
mutate()
alpha = 127
})
.setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int -> appUpdateManager.completeUpdate() }
.setNegativeButton(R.string.no, null)
.create()
.apply { setCanceledOnTouchOutside(false) }
.show()
}
}
installStateUpdatedListener = object : InstallStateUpdatedListener {
override fun onStateUpdate(state: InstallState) {
if (state.installStatus() == InstallStatus.DOWNLOADED)
showCompleteUpdateDialog()
else if (state.installStatus() == InstallStatus.INSTALLED)
appUpdateManager.unregisterListener(this)
}
}.also { appUpdateManager.registerListener(it) }
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
val clientVersionStalenessDays = appUpdateInfo.clientVersionStalenessDays()
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)
&& clientVersionStalenessDays != null
&& clientVersionStalenessDays >= DAYS_FOR_FLEXIBLE_UPDATE) {
try {
activity?.let { activity ->
if (!activity.isFinishing)
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
activity,
REQUEST_CODE_APP_UPDATE)
}
} catch (e: SendIntentException) {
FirebaseCrashlytics.getInstance().recordException(e)
}
} else if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED)
showCompleteUpdateDialog()
}
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private fun onStop() {
installStateUpdatedListener?.let { appUpdateManager.unregisterListener(it) }
}
companion object {
const val REQUEST_CODE_APP_UPDATE = 11
const val DAYS_FOR_FLEXIBLE_UPDATE = 1
}
}
In Activity:
GoogleUpdater(this).apply { checkUpdate() }

Get current location Android Kotlin

I try to get the current Location with GM API in my application (using Android Studio). But if i click the button which triggers the getLocation() funktion, i always end up in the catch{} block and i dont know why.
My mobile device is connected for testing.
Here is the getLocation() Funktion:
fun getLocation() {
var locationManager = getSystemService(LOCATION_SERVICE) as LocationManager?
var locationListener = object : LocationListener{
override fun onLocationChanged(location: Location?) {
var latitute = location!!.latitude
var longitute = location!!.longitude
Log.i("test", "Latitute: $latitute ; Longitute: $longitute")
}
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
}
override fun onProviderEnabled(provider: String?) {
}
override fun onProviderDisabled(provider: String?) {
}
}
try {
locationManager!!.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0L, 0f, locationListener)
} catch (ex:SecurityException) {
Toast.makeText(applicationContext, "Fehler bei der Erfassung!", Toast.LENGTH_SHORT).show()
}
}
Here is the onCreate Funktion:
class CurrentLocationActivity : AppCompatActivity() {
lateinit var mapFragment : SupportMapFragment
lateinit var googleMap : GoogleMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_current_location)
//Karte erstellen
mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(OnMapReadyCallback {
googleMap = it
})
//OnClickListener um Location zu speichern
btnGetCurrentLocation.setOnClickListener {
getLocation()
}
}
Try as follow
Step 1. Put on your AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com....">
<!-- This line -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application ... />
</manifest>
Step 2. Put it above your location request
import android.Manifest
import android.content.pm.PackageManager
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
...
fun getLocation() {
...
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
PERMISSION_REQUEST_ACCESS_FINE_LOCATION)
return
}
locationManager!!.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0L, 0f, locationListener)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST_ACCESS_FINE_LOCATION) {
when (grantResults[0]) {
PackageManager.PERMISSION_GRANTED -> getLocation()
PackageManager.PERMISSION_DENIED -> //Tell to user the need of grant permission
}
}
}
companion object {
private const val PERMISSION_REQUEST_ACCESS_FINE_LOCATION = 100
}
The LocationManager will throw a SecurityException if the location permission has not been granted.
Information on adding the location permissions to your app can be found here.

How to check permission is granted in ViewModel?

I need to ask permission for contacts and when application starts I'm asking,in ViewModel part I need to call method which requires permission. I need to check permission is granted by user or not and then call, but for checking permission I need to have access Activity. while in my ViewModel I don't have a reference to Activity and don't want to have, How I can overcome, the problem?
I just ran into this problem, and I decided to use make use of LiveData instead.
Core concept:
ViewModel has a LiveData on what permission request needs to be made
ViewModel has a method (essentially callback) that returns if permission is granted or not
SomeViewModel.kt:
class SomeViewModel : ViewModel() {
val permissionRequest = MutableLiveData<String>()
fun onPermissionResult(permission: String, granted: Boolean) {
TODO("whatever you need to do")
}
}
FragmentOrActivity.kt
class FragmentOrActivity : FragmentOrActivity() {
private viewModel: SomeViewModel by lazy {
ViewModelProviders.of(this).get(SomeViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
......
viewModel.permissionRequest.observe(this, Observer { permission ->
TODO("ask for permission, and then call viewModel.onPermissionResult aftwewards")
})
......
}
}
I have reworked the solution. The PermissionRequester object is everything you need to request permissions from any point where you have at least an application context. It uses its helper PermissionRequestActivity to accomplish this job.
#Parcelize
class PermissionResult(val permission: String, val state: State) : Parcelable
enum class State { GRANTED, DENIED_TEMPORARILY, DENIED_PERMANENTLY }
typealias Cancellable = () -> Unit
private const val PERMISSIONS_ARGUMENT_KEY = "PERMISSIONS_ARGUMENT_KEY"
private const val REQUEST_CODE_ARGUMENT_KEY = "REQUEST_CODE_ARGUMENT_KEY"
object PermissionRequester {
private val callbackMap = ConcurrentHashMap<Int, (List<PermissionResult>) -> Unit>(1)
private var requestCode = 256
get() {
requestCode = field--
return if (field < 0) 255 else field
}
fun requestPermissions(context: Context, vararg permissions: String, callback: (List<PermissionResult>) -> Unit): Cancellable {
val intent = Intent(context, PermissionRequestActivity::class.java)
.putExtra(PERMISSIONS_ARGUMENT_KEY, permissions)
.putExtra(REQUEST_CODE_ARGUMENT_KEY, requestCode)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
callbackMap[requestCode] = callback
return { callbackMap.remove(requestCode) }
}
internal fun onPermissionResult(responses: List<PermissionResult>, requestCode: Int) {
callbackMap[requestCode]?.invoke(responses)
callbackMap.remove(requestCode)
}
}
class PermissionRequestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
requestPermissions()
}
}
private fun requestPermissions() {
val permissions = intent?.getStringArrayExtra(PERMISSIONS_ARGUMENT_KEY) ?: arrayOf()
val requestCode = intent?.getIntExtra(REQUEST_CODE_ARGUMENT_KEY, -1) ?: -1
when {
permissions.isNotEmpty() && requestCode != -1 -> ActivityCompat.requestPermissions(this, permissions, requestCode)
else -> finishWithResult()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
val permissionResults = grantResults.zip(permissions).map { (grantResult, permission) ->
val state = when {
grantResult == PackageManager.PERMISSION_GRANTED -> State.GRANTED
ActivityCompat.shouldShowRequestPermissionRationale(this, permission) -> State.DENIED_TEMPORARILY
else -> State.DENIED_PERMANENTLY
}
PermissionResult(permission, state)
}
finishWithResult(permissionResults)
}
private fun finishWithResult(permissionResult: List<PermissionResult> = listOf()) {
val requestCode = intent?.getIntExtra(REQUEST_CODE_ARGUMENT_KEY, -1) ?: -1
PermissionRequester.onPermissionResult(permissionResult, requestCode)
finish()
}
}
Usage:
class MyViewModel(application: Application) : AndroidViewModel(application) {
private val cancelRequest: Cancellable = requestPermission()
private fun requestPermission(): Cancellable {
return PermissionRequester.requestPermissions(getApplication(), "android.permission.SEND_SMS") {
if (it.firstOrNull()?.state == State.GRANTED) {
Toast.makeText(getApplication(), "GRANTED", Toast.LENGTH_LONG).show()
} else {
Toast.makeText(getApplication(), "DENIED", Toast.LENGTH_LONG).show()
}
}
}
override fun onCleared() {
super.onCleared()
cancelRequest()
}
}
I did something like this:
create an abstract class that extends AndroidViewModel which gives you access to the application context:
abstract class BaseViewModel(application: Application) : AndroidViewModel(application), CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
Now, create your view model by extending the BaseViewModel class and you will have access to the application context
class AdminViewModel(application: Application) : BaseViewModel(application) {
.....
}
Now you always have access to a Context that you can use to get access to resources.

Categories

Resources