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
Related
I have an app that uses a Worker to update its services via the internet.
However, the worker is not getting triggered.
Worker.kt:
class MyWorker(
private val container: AppContainer,
ctx: Context,
params: WorkerParameters
) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result {
return withContext(Dispatchers.IO) {
return#withContext try {
val response = container.onlineRepository.getData()
// Load the data
container.offlineRepository.load(
data = response.data
)
Result.success()
} catch (throwable: Throwable) {
Log.e(
TAG, throwable.message, throwable
)
Result.failure()
}
}
}
}
DataActivity.kt:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val worker = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkManager.getInstance(this).enqueue(worker)
setContent {
DataApp()
}
}
When i check the logs, nothing is being logged because it is not entering the doWork()
Can someone please help ?
In yourMyWorker class constructor, you are requiring thecontainer: AppContainer argument which is not supplied on instantiation. It's better to use WorkerParameters to achieve this.
You could use this:
// Passing params
Data.Builder data = new Data.Builder();
data.putString("my_key", my_string);
val worker = OneTimeWorkRequestBuilder<MyWorker>()
.setInputData(data.build())
.build()
WorkManager.getInstance(this).enqueue(worker)
However, WorkManager's Data class only accepts some specific types as values as explained in the reference documentation.
On top of that, there's a size limit of about 10 KB, specified by the constant MAX_DATA_BYTES.
If the data is not too big, you may want to serialize it to a String and use that as inputData in your WorkRequest.
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?
I have the below code of a slow loading image
class MainActivity : AppCompatActivity() {
private lateinit var job: Job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val imageLoader = ImageLoader.Builder(this)
.componentRegistry { add(SvgDecoder(this#MainActivity)) }
.build()
job = MainScope().launch {
try {
val request = ImageRequest.Builder(this#MainActivity)
.data("https://restcountries.eu/data/afg.svg")
.build()
val drawable = imageLoader.execute(request).drawable
Log.d("TrackLog", "Loaded")
findViewById<ImageView>(R.id.my_view).setImageDrawable(drawable)
} catch (e: CancellationException) {
Log.d("TrackLog", "Cancelled job")
}
}
}
override fun onDestroy() {
super.onDestroy()
// job.cancel()
}
}
If I exit the activity before the image loaded completed, I thought I should manually perform job.cancel() to get the coroutine canceled.
However, even when I commented out the job.cancel(), the job still get canceled when I exit MainActivity.
This is also true when I use either GlobalScope or even use a global variable scope and job.
val myScope = CoroutineScope(Dispatchers.IO)
private lateinit var job: Job
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val imageLoader = ImageLoader.Builder(this)
.componentRegistry { add(SvgDecoder(this#MainActivity)) }
.build()
job = myScope.launch {
try {
val request = ImageRequest.Builder(this#MainActivity)
.data("https://restcountries.eu/data/afg.svg")
.build()
val drawable = imageLoader.execute(request).drawable
Log.d("TrackLog", "Loaded")
findViewById<ImageView>(R.id.my_view).setImageDrawable(drawable)
} catch (e: CancellationException) {
Log.d("TrackLog", "Cancelled job")
}
}
}
override fun onDestroy() {
super.onDestroy()
// job.cancel()
}
}
I'm puzzled how did the job get canceled when we exit the Activity even when I don't call job.cancel().
Apparently, because my request is made of this#MainActivity
val request = ImageRequest.Builder(this#MainActivity)
.data("https://restcountries.eu/data/afg.svg")
.build()
hence, when exiting, the this#MainActivity is killed, hence the request also got terminated and perhaps canceled?
If we use baseContext
val request = ImageRequest.Builder(baseContext)
.data("https://restcountries.eu/data/afg.svg")
.build()
then we have to manually cancel the job during onDestroy
Therefore it is always safer to use lifecycleScope
I would like to open a new activity when phoneViewModel and ScanViewModel are instantiated. They are instantiated by calling an async function InitialRead(). I'm logging each step, atm they are logged as done3 => done2 => done1
I would like to have them in this order:
done1 => done2 => done3
I have following code:
class MainBusinessActivity : AppCompatActivity() {
private lateinit var scanViewModel: ScanViewModel
private lateinit var phoneViewModel: PhoneViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_business)
}
private fun startEntitySetListActivity() = GlobalScope.async {
val sapServiceManager = (application as SAPWizardApplication).sapServiceManager
sapServiceManager?.openODataStore {
phoneViewModel = ViewModelProvider(this#MainBusinessActivity).get(PhoneViewModel::class.java).also {it.initialRead{Log.e("done", "done1")}}
scanViewModel = ViewModelProvider(this#MainBusinessActivity).get(ScanViewModel::class.java).also {it.initialRead{Log.e("done", "done2")}}
}
}
override fun onResume() {
super.onResume()
//startEntitySetListActivity()
runBlocking {
startEntitySetListActivity().await()
val intent = Intent(this#MainBusinessActivity, HomeActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
Log.e("done", "done3")
startActivity(intent)
}
}
}
What am I doing wrong? Can someone correct my code?
Never use runBlocking in an Android app. runBlocking completely defeats the purpose of using coroutines, and can lead to an ANR. You also probably should never use GlobalScope, which leads to UI leaks. You might possibly need it for some kind of long-running task that doesn't make sense to put in a service but doesn't have dependency on any UI components, but I can't think of any examples
You also shouldn't be instantiating your ViewModels in the background. That should be done in onCreate().
Make this function a suspend function, and it can break down the two tasks in the background simultaneously before returning.
Start your coroutine with lifecycleScope.
Assuming sapServiceManager?.openODataStore is an asynchronous task that takes a callback, you will need to wrap it in suspendCoroutine.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_business)
phoneViewModel = ViewModelProvider(this#MainBusinessActivity).get(PhoneViewModel::class.java)
scanViewModel = ViewModelProvider(this#MainBusinessActivity).get(ScanViewModel::class.java)
}
private suspend fun startEntitySetListActivity() = coroutineScope {
val sapServiceManager = (application as SAPWizardApplication).sapServiceManager
sapServiceManager ?: return
suspendCoroutine<Unit> { continuation ->
sapServiceManager.openODataStore { continuation.resume(Unit) }
}
listOf(
launch {
phoneViewModel.initialRead{Log.e("done", "done1")}
},
launch {
scanViewModel.initialRead{Log.e("done", "done2")}
}
).joinAll()
}
override fun onResume() {
super.onResume()
lifecycleScope.launch {
startEntitySetListActivity()
val intent = Intent(this#MainBusinessActivity, HomeActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
Log.e("done", "done3")
startActivity(intent)
}
}
Lets say that i have an activity that starts a worker. inside the worker i do a pseudo suspend proccess and then i print out a result from the database. Here is the code
The activity which starts the worker is
class SplashActivity: BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
val oneTimeRequest = OneTimeWorkRequest.Builder(MyWorker::class.java).setInputData(Data.Builder().apply {
putInt("data", 1)
}.build()).addTag("worktag").build()
WorkManager.getInstance(applicationContext).enqueue(oneTimeRequest)
}
}
The worker is the below
class MyWorker #AssistedInject constructor(
#Assisted private val appContext: Context,
#Assisted private val params: WorkerParameters,
private val serverRepository: ServerRepository
) : CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
GlobalScope.launch {
for (i in 0..10) {
println("$i")
delay(1000)
}
val servers = serverRepository.getServers()
runOnUiThread {
Toast.makeText(appContext, "${servers.firstOrNull()?.serverAddress}", Toast.LENGTH_SHORT).show()
}
}
return Result.success()
}
}
So the result is that i see in the logcat the system.out with 1,2,3... and then i see a toast messages.
However, when i totally kill the app from the recent while the counter still counts, i never see the toast message.
Why is this happening since i have a GlobalScope coroutine?
And what is the right way to do this??
I was trying to achieve a similar goal. I managed my work by using ForegroundService.
You can find more here
https://developer.android.com/guide/components/foreground-services