refactor: Replace custom worker factory with HiltWorkerFactory (#517)
Removes the need for a separate `WorkerModule` and factory methods in each worker.
This commit is contained in:
parent
442f3bc80c
commit
4762411147
|
@ -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)
|
||||
|
|
|
@ -212,17 +212,11 @@
|
|||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<!-- disable automatic WorkManager initialization -->
|
||||
<!-- Disable automatic WorkManager initialization, use HiltWorkerFactory -->
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
<meta-data
|
||||
android:name="androidx.work.WorkManagerInitializer"
|
||||
android:value="androidx.startup"
|
||||
tools:node="remove" />
|
||||
</provider>
|
||||
tools:node="remove" />
|
||||
|
||||
</application>
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
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<out ListenableWorker>)
|
||||
|
||||
@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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
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<Class<out ListenableWorker>, @JvmSuppressWildcards Provider<ChildWorkerFactory>>,
|
||||
) : 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
|
||||
}
|
||||
}
|
|
@ -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" }
|
||||
|
|
Loading…
Reference in New Issue