I see from Server side that Android calls multiple /test API requests - looks like never-ending for loop.
This API is defined only in TestWorker class:
class TestWorker(
context: Context,
workerParams: WorkerParameters
) : Worker(context, workerParams) {
override fun doWork(): Result {
callApiRequest()
return Result.success()
}
private fun callApiRequest() {
XUseCase.execute()
.backgroundToMainThread()
.subscribe(
{ ->
..
},
{
..
}
)
}
companion object {
const val REPEAT_INTERVAL_IN_HOURS = 3L
}
}
private fun initTestWorker() {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val workerRequest = PeriodicWorkRequestBuilder<TestWorker>(TestWorker.REPEAT_INTERVAL_IN_HOURS, TimeUnit.HOURS)
.setConstraints(constraints)
.build()
WorkManager
.getInstance(this)
.enqueueUniquePeriodicWork("", ExistingPeriodicWorkPolicy.KEEP, workerRequest)
}
initTestWorker() gets called in MyApp onCreate() method. I'm not sure how, but maybe someone has experienced similar behaviour, from the front end I can not reproduce the issue. Any loop experiences using Worker?
Related
Following Android's documentation, I created the following minimal viable example:
#RunWith(AndroidJUnit4::class)
class UpdatePlatformEndpointTest {
private lateinit var context: Context
#Before
fun setUp() {
context = ApplicationProvider.getApplicationContext()
val config = Configuration.Builder()
.setExecutor(SynchronousExecutor())
.build()
WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
}
#Test
fun example() {
val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext())
val workRequest = OneTimeWorkRequestBuilder<W>()
.build()
val operation = workManager.enqueue(workRequest)
operation.result.get()
val workInfo = workManager.getWorkInfoById(workRequest.id).get()
val outputData = workInfo.outputData
assertEquals(workInfo.state, WorkInfo.State.SUCCEEDED)
}
class W(
appContext: Context,
params: WorkerParameters
) : Worker(appContext, params) {
override fun doWork(): Result {
Thread.sleep(5000)
return Result.success()
}
}
}
This works, the tests takes about 5 seconds to complete now. However, changing the Worker class W to the following, the test will fail because the CoroutineWorker has the state of RUNNING.
class W(
appContext: Context,
params: WorkerParameters
) : CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
delay(5000)
return Result.success()
}
}
A quick workaround would be to extend the Worker class and add a runBlocking inside of it.
class W(
appContext: Context,
params: WorkerParameters
) : Worker(appContext, params) {
override fun doWork(): Result = runBlocking {
delay(5000)
return Result.success()
}
}
How could I create CoroutineWorkers and still have a working test setup?
P.S. I created a bug ticket in Google's IssueTracker, however I would love to have workaround that could also be documented for other developers. The bug ticket also contains a sample project to tinker with this issue.
This is the code I am using to setup the work task in my main activity:
val apiFetchBuilder = PeriodicWorkRequestBuilder<APIWorker>(timeValue, TimeUnit.MINUTES) // timeValue = 1 for testing
val apiFetchWorker = apiFetchBuilder.build()
val instance = WorkManager.getInstance(this)
instance.enqueueUniquePeriodicWork(Constants.background_work, ExistingPeriodicWorkPolicy.KEEP, apiFetchWorker)
and then my work manager is setup as follows:
class APIWorker(context: Context, workerParams: WorkerParameters): Worker(context, workerParams){
override fun doWork(): Result {
Log.d("WorkManager", "doWork: ")
//Do API Work here
return Result.success()
}
The logcat isn't registering this call at all no matter how long I wait or how many times I open the main activity to enqueue the work manager
Hi everyone. As the title suggests, I need to take the time when WorkManager finishes its work (every 60 seconds) to perform other operations. At the moment I know that I can't use it for jobs less than 15 minutes, but by trying some different way I can get around the problem. But the fact remains that I need to figure out when the OneTimeWorkRequest command timer ends. What do you suggest to me?
Here my code:
MainActivity:
class MAinActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setPeriodicallySendingLogs()
}
private fun setPeriodicallySendingLogs() {
val workManager = WorkManager.getInstance(this)
val sendingLog = PeriodicWorkRequestBuilder<SendLogWorker>(15, TimeUnit.MINUTES)
.build()
workManager.enqueue(sendingLog)
workManager.getWorkInfoByIdLiveData(sendingLog.id)
.observe(this, Observer { workInfo ->
Log.d("WM", " info${workInfo.state.name}")
})
}
}
Worker:
class SendLogWorker(private val context: Context, userParameters: WorkerParameters) :
Worker(context, userParameters) {
override fun doWork(): Result {
val mywork = OneTimeWorkRequest.Builder(SendLogWorker::class.java)
.setInitialDelay(1, TimeUnit.MINUTES)
.build()
WorkManager.getInstance(context).enqueue(mywork)
return Result.success()
}
}
workManager.getWorkInfoByIdLiveData(syncWorker.id)
.observe(getViewLifecycleOwner(), workInfo -> {
if (workInfo.getState() != null &&
workInfo.getState() == WorkInfo.State.SUCCEEDED) {
Snackbar.make(requireView(),
R.string.work_completed, Snackbar.LENGTH_SHORT)
.show();
}
});
https://developer.android.com/topic/libraries/architecture/workmanager/how-to/managing-work
Help me out please.
I have a simpe Retrofit->Repository->MVVM->Fragment app.
What it does is provides current weather from OpenWeatherApi.
I have my LiveData in my ViewModel observed via Data Binding.
It all works fine.
Issue:
Now I want a background service which updates weather data every 20 minutes.
Im trying to implement this with WorkManager.
And the problem is that I don’t know how to access my ViewModel and how to update LiveData there.
Main question:
How can I update data from Retrofit periodically in MVVM using WorkManager?
Here is my code:
ViewModel:
class CurrentViewModel(application: Application) : AndroidViewModel(application) {
val repo = mRepository.getInstance(application)
val context = application
//This is data i am observing by Data Binding
var currentWeather: LiveData<CurrentWeather>? = repo.getCurrentWeather()
//This method does not seem work when called explicitly btw.
fun updateWeather(){
currentWeather = repo.getCurrentWeather()
}
}
Repository:
class mRepository private constructor(private val context: Context) {
val retrofitClient = RetrofitResponseProvider()
//From here I receive LiveData
fun getCurrentWeather(): LiveData<CurrentWeather>? {
return retrofitClient.getCurrentWeather()
}
}
Retrofit:
class RetrofitResponseProvider {
fun getCurrentWeather(): LiveData<CurrentWeather>? {
val currentWeatherLivedata: MutableLiveData<CurrentWeather> by lazy {
MutableLiveData<CurrentWeather>()
}
val forecast = RetrofitClientInstanceProvider.getRetrofitInstance()?.create(WeatherApiService::class.java)
val call = forecast?.getCurrentWeather()
call?.enqueue(object : Callback<CurrentWeather> {
override fun onResponse(call: Call<CurrentWeather>, response: Response<CurrentWeather>) {
currentWeatherLivedata.value = response.body()
}
override fun onFailure(call: Call<CurrentWeather>, t: Throwable) {
}
})
return currentWeatherLivedata
}
WorkManager:
class MyWorker(val context: Context, val workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
//This is what I want it to do somehow
viewModel.updateWeatherData()
return Result.success()
}
}
I am trying to make API call from doWork() method of WorkManager. I receive MutableLiveData with list from response. How to set this complex object as output from WorkManager.
Please find below implementation for the same :
class FetchWorkManager(context: Context, params: WorkerParameters): Worker(context,params) {
var postInfoLiveData: LiveData<List<PostInfo>> = MutableLiveData()
#SuppressLint("RestrictedApi")
override fun doWork(): Result {
fetchInfoFromRepository()
//setting output data
val data = Data.Builder()
.putAll(postInfoLiveData)
//.put("liveData",postInfoLiveData)
.build()
return Result.success(data)
}
fun fetchInfoFromRepository(){
val retrofitRepository = RetrofitRepository()
postInfoLiveData = retrofitRepository.fetchPostInfoList()
}
}
Can anyone help me in resolving this issue.
i am not sure but it should be like this :)
workManager?.getWorkInfoByIdLiveData(oneTimeWorkRequest.id)
?.observe(this, Observer {
if (it?.state == null)
return#Observer
when (it.state) {
State.SUCCEEDED -> {
val successOutputData = it.outputData
}
State.FAILED -> {
val failureOutputData = it.outputData
}
}
})
It is not intended behaviour to return result from Worker with LiveData member. The result from the Worker should be returned as a return value of startWork method. To construct Result object with some data ListenableWorker.Result.success method can be used.
const val WORKER_RESULT_INT = "WORKER_RESULT_INT"
class WorkerWithOutput(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
// do some work
return Result.success(Data.Builder().putInt(WORKER_RESULT_INT, 123).build())
}
}
And to get this data from outside one of getWorkInfoXXX methods should be used.
fun getResult(context: Context, owner: LifecycleOwner, id: UUID) {
WorkManager.getInstance(context)
.getWorkInfoByIdLiveData(id)
.observe(owner, Observer {
if (it.state == WorkInfo.State.SUCCEEDED) {
val result = it.outputData.getInt(WORKER_RESULT_INT, 0)
// do something with result
}
})
}
Activity or fragment can be passed as LifecycleOwner (depending on your case). WorkRequest.getId is used to get id of the work.
It is worth noting that there is ListenableWorker.setProgressAsync which also can be useful in such circumstances.
I am not sure if this would work since I have not tried it yet. and I know it is a late answer but I would encourage you to try to use CoroutineWorker as below:
class MyWorker(context: Context, params: WorkerParameters):
CoroutineWorker(context, params){
override suspend fun doWork(): Result {
val data = withContext(Dispatchers.IO) {
// you can make network request here (best practice?)
return#withContext fetchInfoFromRepository()
// make sure that fetchInfoFromRepository() returns LiveData<List<PostInfo>>
}
/* Then return it as result with a KEY (DATA_KEY) to use in UI. */
val result = workDataOf(DATA_KEY to data)
return Result.success(result)
}
}
ref: https://developer.android.com/topic/libraries/architecture/workmanager/advanced/coroutineworker