Background location using WorkManager in Android 13 returns null when app is closed

18 Views Asked by At

Manifest file :

        <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

<application
        android:name=".LocationApp"
        android:allowBackup="true"
        android:usesCleartextTraffic="true"
        android:networkSecurityConfig="@xml/network_security_config"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@drawable/beeriders"
        android:label="@string/app_name"
        android:roundIcon="@drawable/beeriders"
        android:supportsRtl="true"
        android:theme="@style/Theme.LocationTracker"
        tools:targetApi="31">

<activity
            android:name=".MainActivity"
            android:exported="true"
            android:theme="@style/Theme.LocationTracker">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <provider
            android:authorities="${applicationId}.androidx-startup"
            android:name="androidx.startup.InitializationProvider"
            android:exported="false"
            android:multiprocess="true"
            tools:node="remove">
        </provider>

    </application>

</manifest>

NetworkModule :

    @dagger.Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Singleton
    @Provides
    fun provideHttpClient(appPreference: PreferenceDataStore): HttpClient {
        return HttpClient(Android){
            install(Logging){
                level= LogLevel.ALL
            }
            install(DefaultRequest){
                url(BASE_URL)
                header(HttpHeaders.ContentType, ContentType.Application.Json)
            }
            install(ContentNegotiation){
                json(Json)
                json(Json {
                    ignoreUnknownKeys = true
                })
            }
        }
    }

    @Provides
    fun provideLocationRepository(@ApplicationContext context: Context): LocationRepository {
        return LocationRepository(context)
    }


    @Singleton
    @Provides
    fun provideApiService(httpClient: HttpClient): ApiService = ApiServiceImpl(httpClient)

    @Singleton
    @Provides
    fun providePreferenceDataStore(@ApplicationContext context: Context): PreferenceDataStore {
        return PreferenceDataStore(context)
    }

    @Provides
    fun provideDispatcher(): CoroutineDispatcher = Dispatchers.Default
}

ApiService :

 interface ApiService {
suspend fun updateLocation(date:String,address:String,time:String,user_id:String): Flow<ApiResult<UpdateLocation>>
}

ApiServiceImpl :

class ApiServiceImpl @Inject constructor(private val httpClient: HttpClient) : ApiService {
override suspend fun updateLocation(date:String,address:String,time:String,user_id:String): Flow<ApiResult<UpdateLocation>> = flow {
        emit(ApiResult.Loading())
        try {
            emit(ApiResult.Success(httpClient.post(BASE_URL + "employee/update") {
                contentType(ContentType.Application.Json)
                setBody(
                    locationupdate(date, address,time,user_id)
                )
            }.body()))
        } catch (e: Exception) {
            e.printStackTrace()
            emit(ApiResult.Error(e.message ?: "Something went wrong"))
        }
    }
}

LocationRepository :

    class LocationRepository(private val context: Context) {

    private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
    private val geoCoder = Geocoder(context)

    @SuppressLint("MissingPermission")
    suspend fun getCurrentLocation(): Location? = suspendCancellableCoroutine { continuation ->
        if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            Log.e("WorkManager","LocationRepository : inside getCurrentLocation")
            fusedLocationClient.getCurrentLocation(Priority.PRIORITY_BALANCED_POWER_ACCURACY, null)
            .addOnSuccessListener { location ->
                Log.e("WorkManager","LocationRepository : inside success ${location}")
                continuation.resume(location)
            }
            .addOnFailureListener { exception ->
                Log.e("WorkManager","LocationRepository : inside failure ${exception}")
                continuation.resumeWithException(exception)
            }
        } else {
            // You might want to handle the case where location permissions are not granted.
            Log.e("WorkManager","LocationRepository : inside permission failure")
            continuation.resume(null)
        }
    }

    suspend fun getAddressFromLocation(location: Location): String? {
        return try {
            val addresses = geoCoder.getFromLocation(location.latitude, location.longitude, 1)
            Log.e("WorkManager","LocationRepository : inside address ${addresses?.firstOrNull()?.getAddressLine(0)}")
            addresses?.firstOrNull()?.getAddressLine(0)
        } catch (e: IOException) {
            // Handle exception or log it
            Log.e("WorkManager","LocationRepository : inside address failure ${e}")
            null
        }
    }

}

LocationUpdateWorker :

    @HiltWorker
class LocationUpdateWorker @AssistedInject constructor(
    @Assisted context: Context,
    @Assisted params: WorkerParameters,
    @Assisted private val locationRepository: LocationRepository,
    @Assisted private val  apiService: ApiService
) : CoroutineWorker(context, params) {

    @SuppressLint("SuspiciousIndentation")
    override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
        try {
    val dateFormat = SimpleDateFormat("dd MMM yyyy", Locale.getDefault())
    val currentDate = Date()
    val formattedDate = dateFormat.format(currentDate)

    val currentTime = LocalTime.now()
    val formatter = DateTimeFormatter.ofPattern("hh:mm a")
    val formattedTime = currentTime.format(formatter)
            Log.e("WorkManager", "From LocationUpdateWorker Inside")
            val preferenceDataStore = PreferenceDataStore(applicationContext)
            val userId = runBlocking { preferenceDataStore.customerId.first() }
            val location = locationRepository.getCurrentLocation()
            Log.e("WorkManager", "location from LocationUpdateWorker : $location")
            location?.let {
                val address = locationRepository.getAddressFromLocation(it)
                Log.e("WorkManager", "address from LocationUpdateWorker : $address")
                Log.e("WorkManager", "formattedDate from LocationUpdateWorker : $formattedDate")
                Log.e("WorkManager", "formattedTime from LocationUpdateWorker : $formattedTime")
                Log.e("WorkManager", "user_id from LocationUpdateWorker : $userId")
                // Now you have the address, update it through the API
               apiService.updateLocation(formattedDate,address.toString(),formattedTime, userId)
                   .collect { apiResult ->
                       when (apiResult) {
                           is ApiResult.Success -> Log.e("WorkManager", "Api Success: ${apiResult.data}")
                           is ApiResult.Error -> Log.e("WorkManager", "Api Error: ${apiResult.error}")
                           is ApiResult.Loading -> Log.e("WorkManager", "Api Loading")
                           is ApiResult.Empty -> Log.e("WorkManager", "Api Empty")
                       }
                   }
                Result.success()
            } ?: Result.failure()
        } catch (e: Exception) {
            Log.e("WorkManager", "error from LocationUpdateWorker : $e")

            Result.failure()
        }
    }
}

MainActivity :

 @AndroidEntryPoint
class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)


        val workRequest = PeriodicWorkRequestBuilder<LocationUpdateWorker>(15, TimeUnit.MINUTES, flexTimeInterval = 15, flexTimeIntervalUnit = TimeUnit.MINUTES).build()
        WorkManager.getInstance(applicationContext).enqueueUniquePeriodicWork("locationUpdateWork", ExistingPeriodicWorkPolicy.KEEP,workRequest)

        WorkManager.getInstance(applicationContext)
            .getWorkInfoByIdLiveData(workRequest.id)
            .observe(this) { workInfo: WorkInfo? ->
                if (workInfo != null) {
                    val state: WorkInfo.State = workInfo.state
                    Log.d("WorkManager", "Status: " + state.name)
                    if (state == WorkInfo.State.FAILED || state == WorkInfo.State.CANCELLED) {
                        Log.d(
                            "WorkManager",
                            "Error/Reason: " + workInfo.outputData.getString("error_key")
                        )
                    }
                } else {
                    Log.d("WorkManager", "Status: null")
                }
            }
        setContent {
            LocationTrackerTheme {
                MainScreenPage()
            }
        }
    }
}

HomeScreen :

    @RequiresApi(Build.VERSION_CODES.Q)
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun HomeScreen(navController: NavController) {

    val context = LocalContext.current
    val backgroundLocationPermissionGranted = remember { mutableStateOf(false) }
    val viewModel = hiltViewModel<LocationViewModel>()

    val requestBackgroundLocationPermissionLauncher =
        rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
            // Your logic for handling the permission result
            backgroundLocationPermissionGranted.value = isGranted
            if (isGranted) {
                viewModel.getCurrentLocation(context)
            } else {
                // Handle the case where the user denies the background location permission
            }
        }

    val preferenceDataStore = PreferenceDataStore(LocalContext.current)
    val employeePageNavigate = remember { mutableStateOf(false) }

    LaunchedEffect(Unit) {
        withContext(Dispatchers.IO) {
            // Your background work here
            preferenceDataStore.getDetails().collect{
                if(it.type=="Employee") {
                    withContext(Dispatchers.Main) {

                    }
                } else if(it.type=="Admin"){
                    withContext(Dispatchers.Main){
                        employeePageNavigate.value = true
                    }
                }
            }
        }
    }

    if(employeePageNavigate.value) {
        navController.navigate(ROUTES.EMPLOYEEHOME.name)
    }

    val updateviewModel = hiltViewModel<UpdateLocationVM>()

    // Observing the LiveData from ViewModel
    val fulladdress = viewModel.fulladdress.observeAsState()

    LaunchedEffect(fulladdress.value) {
        val dateFormat = SimpleDateFormat("dd MMM yyyy", Locale.getDefault())
        val currentDate = Date()
        val formattedDate = dateFormat.format(currentDate)

        val currentTime = LocalTime.now()
        val formatter = DateTimeFormatter.ofPattern("hh:mm a")
        val formattedTime = currentTime.format(formatter)
        fulladdress.value?.let { address ->
            if (address.isNotEmpty()) {
                // Make API call to update the address
                // This should be a function in your ViewModel or Repository
                Log.e("data","address : "+address)
                updateviewModel.locationupdate(formattedDate,address,formattedTime, user_id){ error, message ->
                    Log.e("data","error : "+error)
                    Log.e("data","message : "+message)
                }
            }
        }
    }

    var enableLocation by remember { mutableStateOf(true) }
    var userArea by remember { mutableStateOf("") }
    var userCity by remember { mutableStateOf("") }
    var userPincode by remember { mutableStateOf("") }
    var userLatitude by remember { mutableStateOf("") }
    var userLongitude by remember { mutableStateOf("") }

    var logoutClicked by remember { mutableStateOf(false) }
    var moveToLogin by remember { mutableStateOf(false) }

    viewModel.fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
    Places.initialize(context.applicationContext, "AIzaSyCw0dPNKi-Ff8G0rcAyds3or0S0vlXUrqI")

    viewModel.placesClient = Places.createClient(context)
    viewModel.geoCoder = Geocoder(context)

    Surface {
        Box{
            Column(modifier = Modifier.padding(20.dp)) {
                Text(text = viewModel.fulladdress.value.toString(), fontFamily = medium)
                Spacer(modifier = Modifier.weight(1f))
                Text(text = "Logout", style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center, modifier =
                Modifier
                    .fillMaxWidth()
                    .clickable { logoutClicked = true }
                    .padding(10.dp)
                    .background(Color.Black)
                    .padding(10.dp)
                    .align(Alignment.CenterHorizontally), color = Color.White)
            }
        }
    }

    if(logoutClicked){
        val userdetails = UserDetails("","")
        LaunchedEffect(Unit) {
            val deferredResult = async(Dispatchers.IO) {
                // Your background work here
                preferenceDataStore.setDetails(userdetails)
            }
            deferredResult.await()
            moveToLogin = true
        }
    }

    if(moveToLogin==true){
        navController.navigate(ROUTES.MOVETOLOGIN.name)
    }

    val locationPermissionState = rememberMultiplePermissionsState(
        listOf (
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.ACCESS_FINE_LOCATION
        )
    )

    LaunchedEffect(Unit) {
        if (locationPermissionState.allPermissionsGranted && !backgroundLocationPermissionGranted.value) {
            if (ActivityCompat.checkSelfPermission(
                    context,
                    Manifest.permission.ACCESS_BACKGROUND_LOCATION
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                // Request background location permission
                requestBackgroundLocationPermissionLauncher.launch(
                    Manifest.permission.ACCESS_BACKGROUND_LOCATION
                )
            } else {
                // Background location permission already granted
                backgroundLocationPermissionGranted.value = true
            }
        }
    }

    val locationSettingsLauncher = rememberLauncherForActivityResult(
        ActivityResultContracts.StartIntentSenderForResult()
    ) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            // User enabled location services, you can proceed
            // Call the method or perform the action you need
            viewModel.fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
            Places.initialize(context.applicationContext, "AIzaSyCw0dPNKi-Ff8G0rcAyds3or0S0vlXUrqI")

            viewModel.placesClient = Places.createClient(context)
            viewModel.geoCoder = Geocoder(context)
            viewModel.getCurrentLocation(context)
        } else {
            // User did not enable location services, handle accordingly
        }
    }

    if(enableLocation) {
        // Check Location Permission enabled or not, if yes -> get current location else ask the permission
        LaunchedEffect(locationPermissionState.allPermissionsGranted) {
            if (locationPermissionState.allPermissionsGranted) {
                if (!backgroundLocationPermissionGranted.value) {
                    requestBackgroundLocationPermissionLauncher.launch(
                        Manifest.permission.ACCESS_BACKGROUND_LOCATION
                    )
                } else {
                    if (locationEnabled(context)) {
                        viewModel.getCurrentLocation(context)
                    } else {
                        requestLocationEnable(viewModel, context, locationSettingsLauncher)
                    }
                }
            } else {
                locationPermissionState.launchMultiplePermissionRequest()
            }
        }
    }

    userArea = viewModel.area
    userCity = viewModel.city
    userPincode = viewModel.pincode
    userLatitude = viewModel.lat
    userLongitude = viewModel.longi

}

private fun locationEnabled(context: Context): Boolean {
    val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
    return LocationManagerCompat.isLocationEnabled(locationManager)
}

private fun requestLocationEnable(vm:LocationViewModel, context: Context, locationSettingsLauncher: ActivityResultLauncher<IntentSenderRequest>) {
    context.let {
        val locationRequest = LocationRequest.create()
        val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
        LocationServices.getSettingsClient(it).checkLocationSettings(builder.build())
            .addOnSuccessListener {
                Log.e("location","location from settings : "+it.locationSettingsStates?.isLocationPresent.toString())
                if (it.locationSettingsStates?.isLocationPresent == true) {
                    Log.e("location","location from settings : "+it.locationSettingsStates?.isLocationPresent.toString())
                    vm.getCurrentLocation(context)
                } else {

                }
            }.addOnFailureListener {
                Log.e("location","location from settings failure: ")

                if (it is ResolvableApiException) {
                    try {
                        locationSettingsLauncher.launch(IntentSenderRequest.Builder(it.resolution).build())
                    } catch (e: IntentSender.SendIntentException) {
                        e.printStackTrace()
                    }
                }
            }
    }
}

when app is closed or minimized , the workmanager tries to get the location in background , but I got log inside LocationRepository

LocationRepository : inside success null

why ? how to solve this issue

I am using vivo Y35 android version 13

but when the app is open , location from LocationRepository is working fine the issue is only when app is closed or minimized

Even I choose Allow all the time

Background Location Enable ScreenShot

0

There are 0 best solutions below