refactor: Use androidx.startup for startup activities (#762)

Move initialisation of WorkManager and Timber in to new `Initializer`
classes, managed by `androidx.startup`.

There's a false-positive `BadConfigurationProvider` lint message because
the `Configuration.Provider` is in the content provider and not the
application class.
This commit is contained in:
Nik Clayton 2024-06-19 14:02:58 +02:00 committed by GitHub
parent b373ee9ffe
commit bd6bc95b01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 203 additions and 24 deletions

View File

@ -73,6 +73,13 @@
column="9"/>
</issue>
<issue
id="BadConfigurationProvider"
message="Expected Application subtype to implement Configuration.Provider">
<location
file="src/main/java/app/pachli/PachliApplication.kt"/>
</issue>
<issue
id="SelectedPhotoAccess"
message="Your app is currently not handling Selected Photos Access introduced in Android 14+"

View File

@ -218,8 +218,22 @@
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
android:exported="false"
tools:node="merge">
<meta-data
android:name="app.pachli.initializer.DependencyGraphInitializer"
android:value="androidx.startup" />
<meta-data
android:name="app.pachli.initializer.TimberInitializer"
android:value="androidx.startup" />
<meta-data
android:name="app.pachli.initializer.WorkManagerInitializer"
android:value="androidx.startup" />
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
</application>
</manifest>

View File

@ -19,14 +19,11 @@ package app.pachli
import android.app.Application
import android.content.Context
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import app.pachli.components.notifications.createWorkerNotificationChannel
import app.pachli.core.activity.LogEntryTree
import app.pachli.core.activity.TreeRing
import app.pachli.core.activity.initCrashReporter
import app.pachli.core.preferences.AppTheme
import app.pachli.core.preferences.NEW_INSTALL_SCHEMA_VERSION
@ -50,18 +47,12 @@ import timber.log.Timber
@HiltAndroidApp
class PachliApplication : Application() {
@Inject
lateinit var workerFactory: HiltWorkerFactory
@Inject
lateinit var localeManager: LocaleManager
@Inject
lateinit var sharedPreferencesRepository: SharedPreferencesRepository
@Inject
lateinit var logEntryTree: LogEntryTree
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
@ -83,12 +74,6 @@ class PachliApplication : Application() {
Security.insertProviderAt(Conscrypt.newProvider(), 1)
when {
BuildConfig.DEBUG -> Timber.plant(Timber.DebugTree())
BuildConfig.FLAVOR_color == "orange" -> Timber.plant(TreeRing)
}
Timber.plant(logEntryTree)
// Migrate shared preference keys and defaults from version to version.
val oldVersion = sharedPreferencesRepository.getInt(PrefKeys.SCHEMA_VERSION, NEW_INSTALL_SCHEMA_VERSION)
if (oldVersion != SCHEMA_VERSION) {
@ -108,12 +93,8 @@ class PachliApplication : Application() {
createWorkerNotificationChannel(this)
WorkManager.initialize(
this,
androidx.work.Configuration.Builder().setWorkerFactory(workerFactory).build(),
)
val workManager = WorkManager.getInstance(this)
// Prune the database every ~ 12 hours when the device is idle.
val pruneCacheWorker = PeriodicWorkRequestBuilder<PruneCacheWorker>(12, TimeUnit.HOURS)
.setConstraints(Constraints.Builder().setRequiresDeviceIdle(true).build())

View File

@ -0,0 +1,47 @@
/*
* Copyright 2024 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 android.content.Context
import app.pachli.initializer.TimberInitializer
import app.pachli.initializer.WorkManagerInitializer
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
/**
* Entry point that allows [androidx.startup.Initializer] implementations
* to inject their dependencies.
*/
@EntryPoint
@InstallIn(SingletonComponent::class)
interface InitializerEntryPoint {
fun inject(timberInitializer: TimberInitializer)
fun inject(workManagerInitializer: WorkManagerInitializer)
companion object {
fun resolve(context: Context): InitializerEntryPoint {
val appContext = context.applicationContext ?: throw IllegalStateException()
return EntryPointAccessors.fromApplication(
appContext,
InitializerEntryPoint::class.java,
)
}
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2024 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.initializer
import android.content.Context
import androidx.startup.Initializer
import app.pachli.di.InitializerEntryPoint
/** Empty root initializer other initialisers can depend on. */
class DependencyGraphInitializer : Initializer<Unit> {
override fun create(context: Context) {
InitializerEntryPoint.resolve(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return emptyList()
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2024 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.initializer
import android.content.Context
import androidx.startup.Initializer
import app.pachli.BuildConfig
import app.pachli.core.activity.LogEntryTree
import app.pachli.core.activity.TreeRing
import app.pachli.di.InitializerEntryPoint
import javax.inject.Inject
import timber.log.Timber
/** Initialise [Timber]. */
class TimberInitializer : Initializer<Unit> {
@Inject
lateinit var logEntryTree: LogEntryTree
override fun create(context: Context) {
InitializerEntryPoint.resolve(context).inject(this)
when {
BuildConfig.DEBUG -> Timber.plant(Timber.DebugTree())
BuildConfig.FLAVOR_color == "orange" -> Timber.plant(TreeRing)
}
Timber.plant(logEntryTree)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return listOf(DependencyGraphInitializer::class.java)
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2024 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.initializer
import android.content.Context
import androidx.hilt.work.HiltWorkerFactory
import androidx.startup.Initializer
import androidx.work.Configuration
import androidx.work.WorkManager
import app.pachli.di.InitializerEntryPoint
import javax.inject.Inject
/** Initialise [WorkManager] with a [HiltWorkerFactory]. */
class WorkManagerInitializer : Initializer<WorkManager>, Configuration.Provider {
@Inject
lateinit var workerFactory: HiltWorkerFactory
override val workManagerConfiguration: Configuration
get() = Configuration.Builder().setWorkerFactory(workerFactory).build()
override fun create(context: Context): WorkManager {
InitializerEntryPoint.resolve(context).inject(this)
WorkManager.initialize(context, workManagerConfiguration)
return WorkManager.getInstance(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return listOf(
DependencyGraphInitializer::class.java,
TimberInitializer::class.java,
)
}
}

View File

@ -19,6 +19,7 @@ androidx-preference = "1.2.1"
androidx-recyclerview = "1.3.2"
androidx-sharetarget = "1.2.0"
androidx-splashscreen = "1.0.1"
androidx-startup = "1.1.1"
androidx-swiperefresh-layout = "1.1.0"
androidx-testing = "2.2.0"
androidx-test-core-ktx = "1.5.0"
@ -143,6 +144,7 @@ androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx
androidx-room-testing = { module = "androidx.room:room-testing", version.ref = "androidx-room" }
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "androidx-recyclerview" }
androidx-sharetarget = { module = "androidx.sharetarget:sharetarget", version.ref = "androidx-sharetarget" }
androidx-startup = { module = "androidx.startup:startup-runtime", version.ref = "androidx-startup" }
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "androidx-swiperefresh-layout" }
androidx-test-core-ktx = { module = "androidx.test:core-ktx", version.ref = "androidx-test-core-ktx" }
androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" }
@ -235,8 +237,7 @@ androidx = ["androidx-core-ktx", "androidx-appcompat", "androidx-fragment-ktx",
"androidx-constraintlayout", "androidx-paging-runtime-ktx", "androidx-viewpager2", "androidx-work-runtime-ktx",
"androidx-core-splashscreen", "androidx-activity", "androidx-media3-exoplayer", "androidx-media3-exoplayer-dash",
"androidx-media3-exoplayer-hls", "androidx-media3-exoplayer-rtsp", "androidx-media3-datasource-okhttp", "androidx-media3-ui",
"androidx-transition",
"android-material"]
"androidx-transition", "android-material", "androidx-startup"]
filemojicompat = ["filemojicompat-core", "filemojicompat-ui", "filemojicompat-defaults"]
glide = ["glide-core", "glide-okhttp3-integration", "glide-animation-plugin"]
lint-api = ["kotlin-stdlib", "lint-api", "lint-checks"]