Reputation: 39
I am making an timer app. I did some google search and found that WorkManager is a great tool for long running tasks and it is easier to show notifications to the user also. And as I use Jetpack Compose to create the app it would be even compatible and reliable.
I have a ViewModel
named TimerViewModel
, which defines and starts the timer. Then for the purpose of extending the Worker
class I have a kotlin class called TimerWorker
. Here's how it looks like.
class TimerWorker(
private val context: Context,
private val workerParams: WorkerParameters
):CoroutineWorker(context, workerParams){
private val timerViewModel: TimerViewModel = TimerViewModel(context.applicationContext as Application)
override suspend fun doWork(): Result {
createNotfication()
timerViewModel.startCountDown()
return Result.success()
}
// Creates a notification
private suspend fun createNotfication() {
setForeground(
ForegroundInfo(
Random.nextInt(),
NotificationCompat.Builder(context, "timerChannel")
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentText("Timer running...")
.setContentTitle("Time is unknown")
.build()
)
)
}
}
Then in the TimerViewModel
I define the work request as follows. I also
get the work info to control the main application.
private val outputWorkInfo: LiveData<List<WorkInfo>>
private val workManager = WorkManager.getInstance(getApplication())
init{
outputWorkInfo = workManager.getWorkInfosForUniqueWorkLiveData("timer")
}
val state = outputWorkInfo
//...
fun runTimer(){ // To start the worker
val timerRequest = OneTimeWorkRequestBuilder<TimerWorker>().build()
workManager.beginUniqueWork(
"timer",
ExistingWorkPolicy.KEEP,
timerRequest
).enqueue()
}
And then in the MainActivity
I call this function as a button onClick()
. Note that before starting the timer I get the selected time from the protodata store. I guess that it's not relavent to this question.
Button(
modifier = Modifier.size(//...),
onClick = {
if(savedSecond != null)
timerViewModel.modifyTime(TimerViewModel.Companion.TimeUnit.SEC, savedSecond ?: 0)
timerViewModel.modifyTime(TimerViewModel.Companion.TimeUnit.MIN, savedMinute ?: 0)
timerViewModel.modifyTime(TimerViewModel.Companion.TimeUnit.HOUR, savedHour ?: 0)
if(!((secs.value ?: 0) == 0 && (minutes.value ?: 0) == 0 && (hours.value ?: 0) == 0)){
if(!isRunning){
timerViewModel.runTimer()
}
}
},
shape = CircleShape,
){//...}
My problem is when I touch the Start
button I get the following error.
E/WM-WorkerWrapper: Work [ id=2b4306ff-3f65-4ed6-b1b9-70167fcacaba, tags={ com.example.timer.TimerWorker } ] failed because it threw an exception/error
java.util.concurrent.ExecutionException: java.lang.RuntimeException: Can't create handler inside thread Thread[DefaultDispatcher-worker-2,5,main] that has not called Looper.prepare()
at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:311)
at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
at java.lang.Thread.run(Thread.java:1012)
Caused by: java.lang.RuntimeException: Can't create handler inside thread Thread[DefaultDispatcher-worker-2,5,main] that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:227)
at android.os.Handler.<init>(Handler.java:129)
at android.os.CountDownTimer$1.<init>(CountDownTimer.java:129)
at android.os.CountDownTimer.<init>(CountDownTimer.java:129)
at com.example.timer.CountDownTimer.TimerViewModel$startCountDown$1.<init>(TimerViewModel.kt:63)
at com.example.timer.CountDownTimer.TimerViewModel.startCountDown(TimerViewModel.kt:63)
at com.example.timer.TimerWorker.doWork(TimerWorker.kt:19)
at com.example.timer.TimerWorker$doWork$1.invokeSuspend(Unknown Source:14)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
It would be grateful if you could explain what causes the error and a fix for that. Thank you!
Upvotes: 1
Views: 627
Reputation: 39
I'm really sorry for bothering and being dumb. I just got to know that ViewModel
s can't be instantiated a Worker
.
Besides WorkManager
is not meant for Timers. As for the documentation;
The system instructed your app to stop your work for some reason. This can happen if you exceed the execution deadline of 10 minutes. The work is scheduled for retry at a later time.
Upvotes: 2