diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c2faf843e..7394f34da 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -116,6 +116,11 @@ dependencies { compileOnly(libs.bundles.room) testCompileOnly(libs.bundles.room) + // @HiltWorker annotation + implementation(libs.androidx.hilt.common) + implementation(libs.androidx.hilt.work) + ksp(libs.androidx.hilt.compiler) + implementation(projects.core.accounts) implementation(projects.core.activity) implementation(projects.core.common) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d2add7046..196362e85 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -212,17 +212,11 @@ android:resource="@xml/file_paths" /> - + - - + tools:node="remove" /> diff --git a/app/src/main/java/app/pachli/PachliApplication.kt b/app/src/main/java/app/pachli/PachliApplication.kt index 1d1c6e48c..884cbedf9 100644 --- a/app/src/main/java/app/pachli/PachliApplication.kt +++ b/app/src/main/java/app/pachli/PachliApplication.kt @@ -20,6 +20,7 @@ package app.pachli import android.app.Application import android.content.Context import androidx.core.content.edit +import androidx.hilt.work.HiltWorkerFactory import androidx.work.Constraints import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.PeriodicWorkRequestBuilder @@ -37,7 +38,6 @@ import app.pachli.util.LocaleManager import app.pachli.util.setAppNightMode import app.pachli.worker.PruneCacheWorker import app.pachli.worker.PruneLogEntryEntityWorker -import app.pachli.worker.WorkerFactory import dagger.hilt.android.HiltAndroidApp import de.c1710.filemojicompat_defaults.DefaultEmojiPackList import de.c1710.filemojicompat_ui.helpers.EmojiPackHelper @@ -51,7 +51,7 @@ import timber.log.Timber @HiltAndroidApp class PachliApplication : Application() { @Inject - lateinit var workerFactory: WorkerFactory + lateinit var workerFactory: HiltWorkerFactory @Inject lateinit var localeManager: LocaleManager @@ -110,9 +110,7 @@ class PachliApplication : Application() { WorkManager.initialize( this, - androidx.work.Configuration.Builder() - .setWorkerFactory(workerFactory) - .build(), + androidx.work.Configuration.Builder().setWorkerFactory(workerFactory).build(), ) val workManager = WorkManager.getInstance(this) diff --git a/app/src/main/java/app/pachli/di/WorkerModule.kt b/app/src/main/java/app/pachli/di/WorkerModule.kt deleted file mode 100644 index 17542f08b..000000000 --- a/app/src/main/java/app/pachli/di/WorkerModule.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2023 Pachli Association - * - * This file is a part of Pachli. - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Pachli; if not, - * see . - */ - -package app.pachli.di - -import androidx.work.ListenableWorker -import app.pachli.worker.ChildWorkerFactory -import app.pachli.worker.NotificationWorker -import app.pachli.worker.PruneCacheWorker -import dagger.Binds -import dagger.MapKey -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import dagger.multibindings.IntoMap -import kotlin.reflect.KClass - -@Retention(AnnotationRetention.RUNTIME) -@MapKey -annotation class WorkerKey(val value: KClass) - -@InstallIn(SingletonComponent::class) -@Module -abstract class WorkerModule { - @Binds - @IntoMap - @WorkerKey(NotificationWorker::class) - internal abstract fun bindNotificationWorkerFactory(worker: NotificationWorker.Factory): ChildWorkerFactory - - @Binds - @IntoMap - @WorkerKey(PruneCacheWorker::class) - internal abstract fun bindPruneCacheWorkerFactory(worker: PruneCacheWorker.Factory): ChildWorkerFactory -} diff --git a/app/src/main/java/app/pachli/worker/NotificationWorker.kt b/app/src/main/java/app/pachli/worker/NotificationWorker.kt index a6ae37fce..0b49938ba 100644 --- a/app/src/main/java/app/pachli/worker/NotificationWorker.kt +++ b/app/src/main/java/app/pachli/worker/NotificationWorker.kt @@ -19,6 +19,7 @@ package app.pachli.worker import android.app.Notification import android.content.Context +import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.ForegroundInfo import androidx.work.WorkerParameters @@ -26,13 +27,15 @@ import app.pachli.R import app.pachli.components.notifications.NOTIFICATION_ID_FETCH_NOTIFICATION import app.pachli.components.notifications.NotificationFetcher import app.pachli.components.notifications.createWorkerNotification -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject import timber.log.Timber /** Fetch and show new notifications. */ -class NotificationWorker( - appContext: Context, - params: WorkerParameters, +@HiltWorker +class NotificationWorker @AssistedInject constructor( + @Assisted appContext: Context, + @Assisted params: WorkerParameters, private val notificationsFetcher: NotificationFetcher, ) : CoroutineWorker(appContext, params) { val notification: Notification = createWorkerNotification(applicationContext, R.string.notification_notification_worker) @@ -44,12 +47,4 @@ class NotificationWorker( } override suspend fun getForegroundInfo() = ForegroundInfo(NOTIFICATION_ID_FETCH_NOTIFICATION, notification) - - class Factory @Inject constructor( - private val notificationsFetcher: NotificationFetcher, - ) : ChildWorkerFactory { - override fun createWorker(appContext: Context, params: WorkerParameters): CoroutineWorker { - return NotificationWorker(appContext, params, notificationsFetcher) - } - } } diff --git a/app/src/main/java/app/pachli/worker/PruneCacheWorker.kt b/app/src/main/java/app/pachli/worker/PruneCacheWorker.kt index c4da51d28..521f440c3 100644 --- a/app/src/main/java/app/pachli/worker/PruneCacheWorker.kt +++ b/app/src/main/java/app/pachli/worker/PruneCacheWorker.kt @@ -19,22 +19,24 @@ package app.pachli.worker import android.app.Notification import android.content.Context +import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.ForegroundInfo -import androidx.work.ListenableWorker import androidx.work.WorkerParameters import app.pachli.R import app.pachli.components.notifications.NOTIFICATION_ID_PRUNE_CACHE import app.pachli.components.notifications.createWorkerNotification import app.pachli.core.accounts.AccountManager import app.pachli.core.database.dao.TimelineDao -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject import timber.log.Timber /** Prune the database cache of old statuses. */ -class PruneCacheWorker( - appContext: Context, - workerParams: WorkerParameters, +@HiltWorker +class PruneCacheWorker @AssistedInject constructor( + @Assisted appContext: Context, + @Assisted workerParams: WorkerParameters, private val timelineDao: TimelineDao, private val accountManager: AccountManager, ) : CoroutineWorker(appContext, workerParams) { @@ -54,13 +56,4 @@ class PruneCacheWorker( private const val MAX_STATUSES_IN_CACHE = 1000 const val PERIODIC_WORK_TAG = "PruneCacheWorker_periodic" } - - class Factory @Inject constructor( - private val timelineDao: TimelineDao, - private val accountManager: AccountManager, - ) : ChildWorkerFactory { - override fun createWorker(appContext: Context, params: WorkerParameters): ListenableWorker { - return PruneCacheWorker(appContext, params, timelineDao, accountManager) - } - } } diff --git a/app/src/main/java/app/pachli/worker/PruneLogEntryEntityWorker.kt b/app/src/main/java/app/pachli/worker/PruneLogEntryEntityWorker.kt index cdecd8522..a857a0364 100644 --- a/app/src/main/java/app/pachli/worker/PruneLogEntryEntityWorker.kt +++ b/app/src/main/java/app/pachli/worker/PruneLogEntryEntityWorker.kt @@ -19,31 +19,39 @@ package app.pachli.worker import android.app.Notification import android.content.Context +import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.ForegroundInfo -import androidx.work.ListenableWorker import androidx.work.WorkerParameters import app.pachli.R import app.pachli.components.notifications.NOTIFICATION_ID_PRUNE_CACHE import app.pachli.components.notifications.createWorkerNotification import app.pachli.core.database.dao.LogEntryDao +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject import java.time.Instant -import javax.inject.Inject import kotlin.time.Duration.Companion.hours +import timber.log.Timber /** Prune the database cache of old statuses. */ -class PruneLogEntryEntityWorker( - appContext: Context, - workerParams: WorkerParameters, +@HiltWorker +class PruneLogEntryEntityWorker @AssistedInject constructor( + @Assisted appContext: Context, + @Assisted workerParams: WorkerParameters, private val logEntryDao: LogEntryDao, ) : CoroutineWorker(appContext, workerParams) { val notification: Notification = createWorkerNotification(applicationContext, R.string.notification_prune_cache) override suspend fun doWork(): Result { - val now = Instant.now() - val oldest = now.minusMillis(OLDEST_ENTRY.inWholeMilliseconds) - logEntryDao.prune(oldest) - return Result.success() + return try { + val now = Instant.now() + val oldest = now.minusMillis(OLDEST_ENTRY.inWholeMilliseconds) + logEntryDao.prune(oldest) + Result.success() + } catch (e: Exception) { + Timber.e(e, "error in PruneLogEntryEntityWorker.doWork") + Result.failure() + } } override suspend fun getForegroundInfo() = ForegroundInfo(NOTIFICATION_ID_PRUNE_CACHE, notification) @@ -52,12 +60,4 @@ class PruneLogEntryEntityWorker( private val OLDEST_ENTRY = 48.hours const val PERIODIC_WORK_TAG = "PruneLogEntryEntityWorker_periodic" } - - class Factory @Inject constructor( - private val logEntryDao: LogEntryDao, - ) : ChildWorkerFactory { - override fun createWorker(appContext: Context, params: WorkerParameters): ListenableWorker { - return PruneLogEntryEntityWorker(appContext, params, logEntryDao) - } - } } diff --git a/app/src/main/java/app/pachli/worker/WorkerFactory.kt b/app/src/main/java/app/pachli/worker/WorkerFactory.kt deleted file mode 100644 index 5bc9856bf..000000000 --- a/app/src/main/java/app/pachli/worker/WorkerFactory.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2023 Pachli Association - * - * This file is a part of Pachli. - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Pachli; if not, - * see . - */ - -package app.pachli.worker - -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerFactory -import androidx.work.WorkerParameters -import javax.inject.Inject -import javax.inject.Provider -import javax.inject.Singleton -import timber.log.Timber - -/** - * Workers implement this and are added to the map in [app.pachli.di.WorkerModule] - * so they can be created by [WorkerFactory.createWorker]. - */ -interface ChildWorkerFactory { - /** Create a new instance of the given worker. */ - fun createWorker(appContext: Context, params: WorkerParameters): ListenableWorker -} - -/** - * Creates workers, delegating to each worker's [ChildWorkerFactory.createWorker] to do the - * creation. - * - * @see [app.pachli.worker.NotificationWorker] - */ -@Singleton -class WorkerFactory @Inject constructor( - private val workerFactories: Map, @JvmSuppressWildcards Provider>, -) : WorkerFactory() { - override fun createWorker( - appContext: Context, - workerClassName: String, - workerParameters: WorkerParameters, - ): ListenableWorker? { - val key = try { - Class.forName(workerClassName) - } catch (e: ClassNotFoundException) { - // Class might be missing if it was renamed / moved to a different package, as - // periodic work requests from before the rename might still exist. Catch and - // return null, which should stop future requests. - Timber.d(e, "Invalid class: %s", workerClassName) - null - } - workerFactories[key]?.let { - return it.get().createWorker(appContext, workerParameters) - } - return null - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f2b053dff..b5d5d61a3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ androidx-constraintlayout = "2.1.4" androidx-core = "1.12.0" androidx-exifinterface = "1.3.7" androidx-fragment = "1.6.2" +androidx-hilt = "1.2.0" androidx-junit = "1.1.5" androidx-lifecycle = "2.7.0" androidx-media3 = "1.2.1" @@ -109,6 +110,9 @@ androidx-emoji2-views-core = { module = "androidx.emoji2:emoji2-views", version. androidx-emoji2-view-helper = { module = "androidx.emoji2:emoji2-views-helper", version.ref = "emoji2" } androidx-exifinterface = { module = "androidx.exifinterface:exifinterface", version.ref = "androidx-exifinterface" } androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" } +androidx-hilt-common = { module = "androidx.hilt:hilt-common", version.ref = "androidx-hilt" } +androidx-hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "androidx-hilt" } +androidx-hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "androidx-hilt" } androidx-lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "androidx-lifecycle" } androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "androidx-lifecycle" } androidx-lifecycle-reactivestreams-ktx = { module = "androidx.lifecycle:lifecycle-reactivestreams-ktx", version.ref = "androidx-lifecycle" }