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
