Ethan Rodrigo
Ethan Rodrigo

Reputation: 39

Can't create handler inside thread Thread[DefaultDispatcher-worker-2,5,main] that has not called Looper.prepare(); Jetpack Compose

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

Answers (1)

Ethan Rodrigo
Ethan Rodrigo

Reputation: 39

I'm really sorry for bothering and being dumb. I just got to know that ViewModels 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

Related Questions