Using this library "androidx.work:work-runtime-ktx:2.5.0" to run a background thread.
But OneTimeWorkRequest is being cancelled everything i exit the screen. What i am trying to acheive is set it run on the application level can someone help please or some hints? here is my code below :
var workManager = WorkManager.getInstance(requireContext())
val data: Data.Builder = Data.Builder().apply {
putLong("inspectionId", id)
}
val task = OneTimeWorkRequest.Builder(NewSyncWorkManager::class.java).apply {
// setConstraints(constraints)
setInputData(data.build())
setInitialDelay(1,TimeUnit.SECONDS)
addTag(id.toString())
}.build()
workManager.enqueue(task)
while NewSyncWorkManager is as following:
class NewSyncWorkManager(context: Context,workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
showNotification()
val foregroundInfo = ForegroundInfo(notificationId, notification!!)
setForeground(foregroundInfo)
if (inspectionId == 0L) {
Log.d("wah", "inspectionId to upload is missing, stopping")
return Result.failure()
}
val propertySections = sectionsRepo.getPropertyLayout(inspectionId)?.reportSections
.....
UPLOAD requests to the server
}
}
Fixed the issue by running the UPLOAD requests to the server inside a GlobalScope
Related
I've seen that there are ways to update an app with Firebase Remote Config. Some sort of "Force Update" Notification. If anyone can explain it to me, that would be great.
How to use Firebase to update your Android App?
There are multiple ways in which you can update an Android app. The first one would be to store data in a database. Firebase has two real-time databases, Cloud Firestore and the Realtime Database. You can one or the other, according to the use case of your app. For that I recommend you check the following resource:
https://firebase.google.com/docs/database/rtdb-vs-firestore
When it comes to Remote Config, please notice that nowadays you can propagate Remote Config updates in real-time. That being said, there is no need to force anything. So I highly recommend that a look at that.
For Force update in a simple case the idea is
with firebase remort config sends the version number which you want for your application to be forced
then compare remort version with the local application version
if there is a mismatch then show a permanent dialog (cancelable=false) with a button when the user clicks on that button to open the application in the play store .
Check out this Small Class created for force update with remort config
class ForceUpdateChecker(private val context: Context, private val onUpdateNeededListener: OnUpdateNeededListener?) {
interface OnUpdateNeededListener {
fun onUpdateNeeded(updateUrl: String?)
}
fun check() {
val remoteConfig = FirebaseRemoteConfig.getInstance()
if (remoteConfig.getBoolean(KEY_UPDATE_REQUIRED)) {
val currentVersion = remoteConfig.getString(KEY_CURRENT_VERSION)
val appVersion = getAppVersion(context)
val updateUrl = remoteConfig.getString(KEY_UPDATE_URL)
if (!TextUtils.equals(currentVersion, appVersion)
&& onUpdateNeededListener != null
) {
onUpdateNeededListener.onUpdateNeeded(updateUrl)
}
}
}
private fun getAppVersion(context: Context): String {
var result = ""
try {
result = context.packageManager
.getPackageInfo(context.packageName, 0).versionName
result = result.replace("[a-zA-Z]|-".toRegex(), "")
} catch (e: PackageManager.NameNotFoundException) {
Log.e(TAG, e.message!!)
}
return result
}
class Builder(private val context: Context) {
private var onUpdateNeededListener: OnUpdateNeededListener? = null
fun onUpdateNeeded(onUpdateNeededListener: OnUpdateNeededListener?): Builder {
this.onUpdateNeededListener = onUpdateNeededListener
return this
}
fun build(): ForceUpdateChecker {
return ForceUpdateChecker(context, onUpdateNeededListener)
}
fun check(): ForceUpdateChecker {
val forceUpdateChecker = build()
forceUpdateChecker.check()
return forceUpdateChecker
}
}
companion object {
private val TAG = ForceUpdateChecker::class.java.simpleName
const val KEY_UPDATE_REQUIRED = "force_update_required"
const val KEY_CURRENT_VERSION = "force_update_current_version"
const val KEY_UPDATE_URL = "force_update_store_url"
fun with(context: Context): Builder {
return Builder(context)
}
}}
Call this like this in baseActivity (or from your landing page just not in splash screen)
ForceUpdateChecker.with(this).onUpdateNeeded(this).check();
In application on create add this
val firebaseRemoteConfig = FirebaseRemoteConfig.getInstance()
// set in-app defaults
val remoteConfigDefaults: MutableMap<String, Any> = HashMap()
remoteConfigDefaults[ForceUpdateChecker.KEY_UPDATE_REQUIRED] = false
remoteConfigDefaults[ForceUpdateChecker.KEY_CURRENT_VERSION] = "1.0"
remoteConfigDefaults[ForceUpdateChecker.KEY_UPDATE_URL] =
"https://play.google.com/store/apps/details?id=com.com.classified.pems"
firebaseRemoteConfig.setDefaultsAsync(remoteConfigDefaults)
firebaseRemoteConfig.fetch(60) // fetch every minutes
.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d(TAG, "remote config is fetched.")
firebaseRemoteConfig.fetchAndActivate()
}
}
I am downloading file with "Coroutine Worker" work Manager, when application is in background the downloading work properly but when application is killed, work Manager stop working.
I tried to keep it alive with a boolean variable in an infinite loop to make it work but it didn't work as well.
class UploadWorker(
private val appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
private var isDownloadCompleted = false
override suspend fun doWork(): Result {
val filName = inputData.getString("filName") ?: ""
val url = inputData.getString("URL") ?: ""
/*when file is downloaded, I change the status of the boolean to true, so it break the
loop*/
//Loop break when download file is completed, occur error, or pause.
while (!isDownloadCompleted) {
Log.e("tag**", "downloaded file $isDownloadCompleted")
}
// Indicate whether the work finished successfully with the Result
return Result.success()
}
}
val imageData = workDataOf("URL" to url, "filName" to filName)
val constraints = Constraints.Builder()
.setRequiresBatteryNotLow(false)
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(false)
.build()
Log.e("tag**", "createReq")
val uploadWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(imageData)
.setConstraints(constraints)
.setInitialDelay(0, TimeUnit.SECONDS)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
I wanted to make it work whether application is killed or in background.
Check if this helps you ->
Kotlin ->
class UploadWorker(
private val appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
private var isDownloadCompleted = false
override suspend fun doWork(): Result {
val filName = inputData.getString("filName") ?: ""
val url = inputData.getString("URL") ?: ""
/*when file is downloaded, I change the status of the boolean to true, so it break the
loop*/
//Loop break when download file is completed, occur error, or pause.
while (!isDownloadCompleted) {
Log.e("tag**", "downloaded file $isDownloadCompleted")
}
// Indicate whether the work finished successfully with the Result
return Result.success()
}
}
Instead of using OneTimeWorkRequest, Try Using PeriodicWorkRequest, Hope it works.
val imageData = workDataOf("URL" to url, "filName" to filName)
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(false)
.setRequiresStorageNotLow(false)
.build()
Log.e("tag**", "createReq")
val uploadWorkRequest: WorkRequest =
PeriodicWorkRequestBuilder<UploadWorker>(2, TimeUnit.HOURS)
.setInputData(imageData)
.setConstraints(constraints)
.setInitialDelay(0, TimeUnit.SECONDS)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
Nothing says that WorkManager will work if the app is killed. If the app is killed it is dead. That's it. What WorkManager ensures is that:
the application is less likely to be killed while a Work is in progress because WorkManager raised its own Service
If the application is killed and respectively a work is stopped - WM will ensure that the Work will be resumed in a later stage and it will finish for sure at some point when the Constraints are met.
All things are good but I forgot to attached a notification with Work Manager.
class UploadWorker(
private val appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
private var isDownloadCompleted = false
override suspend fun doWork(): Result {
notification = NotificationCompat.Builder(
applicationContext,
appContext.getString(R.string.channel_name)
)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(getString(R.string.app_name))
.setContentText("0MB/ $fileSize")
.setOnlyAlertOnce(true)
.setProgress(100, 0, false)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setOngoing(true)
.setAutoCancel(false)
.setDefaults(NotificationCompat.DEFAULT_ALL)
val filName = inputData.getString("filName") ?: ""
val url = inputData.getString("URL") ?: ""
//attach your notification, so workmanager will be killed
setForegroundAsync(ForegroundInfo(id, notification.build()))
/*when file is downloaded, I change the status of the boolean to true, so it break the
loop*/
//Loop break when download file is completed, occur error, or pause.
while (!isDownloadCompleted) {
Log.e("tag**", "downloaded file $isDownloadCompleted")
}
// Indicate whether the work finished successfully with the Result
return Result.success()
}
}
This is My Code :
#RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.home_activity)
loadProductForTheFirst()
#RequiresApi(Build.VERSION_CODES.M)
private fun hasNetworkAvilable(context: Context): Boolean {
val service = Context.CONNECTIVITY_SERVICE
val manager = context.getSystemService(service) as ConnectivityManager
val network = manager.activeNetwork
return (network != null)
}
#RequiresApi(Build.VERSION_CODES.M)
fun loadProductForTheFirst(){
swipeRefreshMain.isRefreshing = true
viewModel.getalldata().observe(this, Observer {
if (!it.isNullOrEmpty()) {
recycler_main.apply {
layoutManager = GridLayoutManager(this#HomeActivity, 2)
adapter = RecyclerAdapterMain(it, this#HomeActivity)
swipeRefreshMain.isRefreshing = false
}
} else {
if (hasNetworkAvilable(this)) {
viewModel.products.observe(this, Observer {
recycler_main.apply {
layoutManager = GridLayoutManager(this#HomeActivity, 2)
adapter = RecyclerAdapterMain(it, this#HomeActivity)
swipeRefreshMain.isRefreshing = false
}
})
viewModel.setup()
} else {
/// in here if the user not internet for loading the products
/// the alert dialog displays .
AlertDialog.Builder(this)
.setTitle("Internet State")
.setMessage("please turn on your internet connection")
.create()
.show()
/// in here I want a method ( workmanager )
// that as soon as the internet be accessible
/// my product will be updated .
}
}
})
}
well , For the first time that user open my app need the internet to load product from api .
So I just want the method like WorkManager to check if the intenrnet avalibility is accessible .
And after that my method will be load from api .
I did some search but could'nt find any useful example of work with workmanager.
anyone can help me with this . ?
I did this code and work for me .
I put it here if someone looking for this method .
I used work manager to get data from api whenever the network is on .
val constraints = Constriants.builder(this)
.setRequiredNetworkType(NetworkType.Connected)
val workManager : WorkManager = WorkManager.getInstance(this)
val oneRequestWork = OneRequestWorker.build(UploadWorker::class.java)
.setconstrints(constraints)
.build
workmanager.enqueue(oneRequestWork)
the Upload worker class :
class UploadWorker(context : Context , param : WorkerParameters) : Worker(context , param)
private val viewModel: ViewModelRoom by lazy {
ViewModelProvider(
ViewModelStore(),
FactoryRoom(RepositoryCart(DataBaseRoom.invoke(applicationContext)))
)
.get(ViewModelRoom::class.java)
}
override fun dowork() : Result {
return try {
viewModel.setup()
Result.success()
} catch (e: Exception) {
Result.failure()
}
I need to cancel/remove my work when it meets a condition. I`ve read that there is a method onStopped() that can be overridden, but it is for simple :Worker and not for CoroutineWorker.
My worker:
class MyJob(appContext: Context, params: WorkerParameters) :
CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
val itemId = inputData.getLong("item_id",0)
val itemDao = ItemDB.getInstance(applicationContext).itemDao()
val itemRepository = ItemRepo(itemsDao)
val item = itemRepository.getItemById(itemId)
val newValue = item.a + item.b
item.a = newValue
itemRepository.updateItem(item)
if(item.a == item.c){
WorkManager.getInstance(applicationContext).cancelUniqueWork("TEST_WORKER")
}
return Result.success()
}
}
EDIT:
My mistake, i forgot to mention that this is a unique periodic work.
You can try to cancel the work by returning Result.failure(); so you can add it under your condition
if(item.a == item.c){
//Cancel work
return Result.failure()
}
Now you need to listen to the work result in your activity/fragment; as you didn't provide this part of the code, I will do some assumptions, and listen to the work result with lifecycle observation model
// instantiate periodic work
final PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(...).build();
// schedule the work
WorkManager.getInstance(this).enqueue(workRequest);
// observe the work
WorkManager.getInstance(this).getWorkInfoByIdLiveData(workRequest.getId())
.observe(this, new Observer<WorkInfo>() {
#Override
public void onChanged(WorkInfo workInfo) {
if (workInfo.getState() == WorkInfo.State.FAILED) {
// cancelling work
WorkManager.getInstance(MainActivity.this).cancelWorkById(workRequest.getId());
}
}
});
I'm trying to cancel a task that hasn't started yet.
My Worker class code:
class TestWork(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
val data = inputData.getInt(KEY_OBJ, -1)
runBlocking {
for (i in 1..3){
Log.d("MyTag", "testWork: $data")
delay(1000)
}
}
return Result.SUCCESS
}
override fun onStopped() {
super.onStopped()
Log.d("MyTag", "stopped")
}
companion object {
const val KEY_OBJ = "key"
val WORK_NAME = "name"
}
}
The code sample that running 3 works and canceling second.
But second work continues.
val data1 = Data.Builder()
.putInt(TestWork.KEY_OBJ, 1)
.build()
val workRequest1 = OneTimeWorkRequest.Builder(TestWork::class.java)
.setInputData(data1)
.addTag("1")
.build()
val data2 = Data.Builder()
.putInt(TestWork.KEY_OBJ, 2)
.build()
val workRequest2 = OneTimeWorkRequest.Builder(TestWork::class.java)
.setInputData(data2)
.addTag("2")
.build()
val data3 = Data.Builder()
.putInt(TestWork.KEY_OBJ, 3)
.build()
val workRequest3 = OneTimeWorkRequest.Builder(TestWork::class.java)
.setInputData(data3)
.addTag("3")
.build()
WorkManager.getInstance().enqueueUniqueWork(
TestWork.WORK_NAME,
ExistingWorkPolicy.APPEND,
workRequest1
)
WorkManager.getInstance().enqueueUniqueWork(
TestWork.WORK_NAME,
ExistingWorkPolicy.APPEND,
workRequest2
)
WorkManager.getInstance().enqueueUniqueWork(
TestWork.WORK_NAME,
ExistingWorkPolicy.APPEND,
workRequest3
)
WorkManager.getInstance().cancelWorkById(workRequest2.id)
How to cancel second work only? While the first is executed
You should use the isStopped() method call inside your onWork method
This is explained in the video presented at the Android Developer Summit. Around minute 15 into the presentation there's a whole section on how to stop work in WorkManager that goes through these details
As I see, you have a mistake in your cancelling line, you have to replace cancelWorkById("2") by cancelAllWorkByTag("2") because you are adding tags .addTag("2") to the Work.
I'm currently using WorkManager and I have tried to cancel works with tags and it works.