Skip to content

Commit 87a1c0d

Browse files
committed
refactor: Migrated from Hilt to Koin.
* Hilt is making slow progress in adapting to KSP.(google/dagger#2349) * Potential plans to support KMP in the future.
1 parent d840cd6 commit 87a1c0d

File tree

91 files changed

+621
-768
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+621
-768
lines changed

app/build.gradle.kts

+5-6
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ plugins {
99
alias(libs.plugins.android.application)
1010
alias(libs.plugins.android.kotlin)
1111
alias(libs.plugins.serialization)
12-
alias(libs.plugins.kapt)
1312
alias(libs.plugins.ksp)
14-
alias(libs.plugins.android.hilt)
1513
}
1614

1715
//region Keystore
@@ -161,10 +159,9 @@ dependencies {
161159
implementation(libs.bundles.androidx.room)
162160
ksp(libs.androidx.room.compiler)
163161

164-
// Hilt
165-
implementation(libs.bundles.hilt)
166-
kapt(libs.hilt.compiler)
167-
kaptAndroidTest(libs.hilt.compiler)
162+
// Koin
163+
implementation(libs.bundles.koin)
164+
ksp(libs.koin.ksp.compiler)
168165

169166
// Jetpack Compose
170167
val composeBom = platform(libs.androidx.compose.bom)
@@ -190,7 +187,9 @@ dependencies {
190187

191188
// Test
192189
testImplementation(libs.junit)
190+
testImplementation(libs.bundles.koin.test)
193191
androidTestImplementation(libs.bundles.androidx.test)
194192
androidTestImplementation(libs.bundles.compose.test)
193+
androidTestImplementation(libs.koin.android.test)
195194
debugImplementation(libs.compose.test.manifest)
196195
}

app/src/androidTest/java/com/crossbowffs/quotelock/CustomTestRunner.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package com.crossbowffs.quotelock
33
import android.app.Application
44
import android.content.Context
55
import androidx.test.runner.AndroidJUnitRunner
6-
import dagger.hilt.android.testing.HiltTestApplication
6+
import com.crossbowffs.quotelock.app.TestApp
77

88
class CustomTestRunner : AndroidJUnitRunner() {
99

1010
override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
11-
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
11+
return super.newApplication(cl, TestApp::class.java.name, context)
1212
}
1313
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.crossbowffs.quotelock.app
2+
3+
import android.app.Application
4+
import com.crossbowffs.quotelock.di.DataModule
5+
import org.koin.android.ext.koin.androidContext
6+
import org.koin.core.context.startKoin
7+
import org.koin.ksp.generated.module
8+
9+
class TestApp : Application() {
10+
11+
override fun onCreate() {
12+
super.onCreate()
13+
startKoin {
14+
androidContext(this@TestApp)
15+
modules(DataModule().module)
16+
}
17+
}
18+
}

app/src/androidTest/java/com/crossbowffs/quotelock/data/modules/wikiquote/WikiquoteRepositoryTest.kt

+7-23
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,18 @@
11
package com.crossbowffs.quotelock.data.modules.wikiquote
22

3-
import android.content.Context
3+
import androidx.test.ext.junit.runners.AndroidJUnit4
44
import com.crossbowffs.quotelock.utils.Xlog
5-
import dagger.hilt.android.qualifiers.ApplicationContext
6-
import dagger.hilt.android.testing.HiltAndroidRule
7-
import dagger.hilt.android.testing.HiltAndroidTest
85
import kotlinx.coroutines.runBlocking
96
import org.junit.Assert
10-
import org.junit.Before
11-
import org.junit.Rule
127
import org.junit.Test
13-
import javax.inject.Inject
8+
import org.junit.runner.RunWith
9+
import org.koin.core.component.KoinComponent
10+
import org.koin.core.component.inject
1411

15-
@HiltAndroidTest
16-
class WikiquoteRepositoryTest {
12+
@RunWith(AndroidJUnit4::class)
13+
class WikiquoteRepositoryTest : KoinComponent {
1714

18-
@get:Rule
19-
var hiltRule = HiltAndroidRule(this)
20-
21-
@Inject
22-
@ApplicationContext
23-
lateinit var context: Context
24-
25-
@Inject
26-
lateinit var repository: WikiquoteRepository
27-
28-
@Before
29-
fun init() {
30-
hiltRule.inject()
31-
}
15+
private val repository: WikiquoteRepository by inject()
3216

3317
@Test
3418
fun testRequestAllQuotes() {

app/src/main/AndroidManifest.xml

+13
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,19 @@
129129
android:resource="@xml/file_provider_paths" />
130130
</provider>
131131

132+
<!-- For Koin.-->
133+
<!-- See https://developer.android.com/topic/libraries/architecture/workmanager/advanced/custom-configuration#remove-default-->
134+
<provider
135+
android:name="androidx.startup.InitializationProvider"
136+
android:authorities="${applicationId}.androidx-startup"
137+
android:exported="false"
138+
tools:node="merge">
139+
<meta-data
140+
android:name="androidx.work.WorkManagerInitializer"
141+
android:value="androidx.startup"
142+
tools:node="remove" />
143+
</provider>
144+
132145
<meta-data
133146
android:name="xposedmodule"
134147
android:value="true" />

app/src/main/java/com/crossbowffs/quotelock/account/SyncAccountManager.kt

+13-8
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,30 @@ import com.crossbowffs.quotelock.account.syncadapter.getSyncTimestamp
1010
import com.crossbowffs.quotelock.account.syncadapter.setServerSyncMarker
1111
import com.crossbowffs.quotelock.data.modules.collections.QuoteCollectionRepository
1212
import com.crossbowffs.quotelock.data.modules.collections.database.QuoteCollectionContract
13-
import com.crossbowffs.quotelock.di.IoDispatcher
13+
import com.crossbowffs.quotelock.di.DISPATCHER_IO
1414
import com.crossbowffs.quotelock.utils.Xlog
1515
import com.crossbowffs.quotelock.utils.className
1616
import com.yubyf.quotelockx.BuildConfig
1717
import kotlinx.coroutines.CoroutineDispatcher
1818
import kotlinx.coroutines.CoroutineScope
1919
import kotlinx.coroutines.launch
20-
import javax.inject.Inject
20+
import org.koin.core.annotation.Named
21+
import org.koin.core.annotation.Single
2122

2223
/**
2324
* @author Yubyf
2425
* @date 2021/6/20.
2526
*/
26-
class SyncAccountManager @Inject constructor(
27+
@Single(createdAtStart = true)
28+
class SyncAccountManager(
2729
private val accountManager: AccountManager,
2830
private val collectionRepository: QuoteCollectionRepository,
29-
@IoDispatcher private val dispatcher: CoroutineDispatcher,
31+
@Named(DISPATCHER_IO) private val dispatcher: CoroutineDispatcher,
3032
) {
3133

34+
private val accountType: String
35+
get() = BuildConfig.APPLICATION_ID + ".account"
36+
3237
fun initialize() {
3338
CoroutineScope(dispatcher).launch {
3439
collectionRepository.getAllStream().collect {
@@ -38,7 +43,8 @@ class SyncAccountManager @Inject constructor(
3843
* changeUri is null.
3944
*/
4045
Xlog.d(TAG, "Data on change, requesting sync...")
41-
ContentResolver.requestSync(currentSyncAccount,
46+
ContentResolver.requestSync(
47+
currentSyncAccount,
4248
QuoteCollectionContract.AUTHORITY, Bundle()
4349
)
4450
}
@@ -57,7 +63,7 @@ class SyncAccountManager @Inject constructor(
5763
clearAccountUserData(account)
5864
}
5965
} else {
60-
account = Account(name, ACCOUNT_TYPE)
66+
account = Account(name, accountType)
6167
accountManager.addAccountExplicitly(account, null, null)
6268
Xlog.d(TAG, "Added account of name $name")
6369
clearAccountUserData(account)
@@ -110,11 +116,10 @@ class SyncAccountManager @Inject constructor(
110116
}
111117

112118
private val currentSyncAccount: Account?
113-
get() = accountManager.getAccountsByType(ACCOUNT_TYPE)
119+
get() = accountManager.getAccountsByType(accountType)
114120
.run { if (isNotEmpty()) this[0] else null }
115121

116122
companion object {
117123
private val TAG = className<SyncAccountManager>()
118-
private const val ACCOUNT_TYPE = BuildConfig.APPLICATION_ID + ".account"
119124
}
120125
}

app/src/main/java/com/crossbowffs/quotelock/account/google/GoogleAccountManager.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import android.content.Context
44
import android.content.Intent
55
import com.crossbowffs.quotelock.data.api.GoogleAccount
66
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
7-
import dagger.hilt.android.qualifiers.ApplicationContext
8-
import javax.inject.Inject
7+
import org.koin.core.annotation.Single
98

109
/**
1110
* @author Yubyf
1211
*/
13-
class GoogleAccountManager @Inject constructor(
14-
@ApplicationContext private val context: Context,
12+
@Single
13+
class GoogleAccountManager(
14+
private val context: Context,
1515
) {
1616

1717
fun checkGooglePlayService(): Boolean {

app/src/main/java/com/crossbowffs/quotelock/account/syncadapter/SyncAdapter.kt

+11-15
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,16 @@ import com.crossbowffs.quotelock.utils.Xlog
1515
import com.crossbowffs.quotelock.utils.className
1616
import com.crossbowffs.quotelock.utils.getDatabaseInfo
1717
import com.yubyf.quotelockx.BuildConfig
18-
import dagger.hilt.EntryPoint
19-
import dagger.hilt.InstallIn
20-
import dagger.hilt.android.EntryPointAccessors
21-
import dagger.hilt.components.SingletonComponent
2218
import kotlinx.coroutines.runBlocking
19+
import org.koin.core.component.KoinComponent
20+
import org.koin.core.component.get
2321

2422
/**
2523
* @author Yubyf
2624
* @date 2021/6/20.
2725
*/
2826
class SyncAdapter(private val mContext: Context, autoInitialize: Boolean) :
29-
AbstractThreadedSyncAdapter(mContext, autoInitialize) {
27+
AbstractThreadedSyncAdapter(mContext, autoInitialize), KoinComponent {
3028

3129
private val mAccountManager: AccountManager = AccountManager.get(mContext)
3230

@@ -40,23 +38,25 @@ class SyncAdapter(private val mContext: Context, autoInitialize: Boolean) :
4038
syncResult.stats.numAuthExceptions++
4139
return
4240
}
43-
val repository = EntryPointAccessors.fromApplication(
44-
mContext.applicationContext, SyncAdapterEntryPoint::class.java).collectionRepository()
41+
val repository: QuoteCollectionRepository = get()
4542
val action = checkBackupOrRestore(account)
4643
val result = when {
4744
action == null -> {
4845
Xlog.d(TAG, "No backup or restore action found. Retry 5 minutes later.")
4946
syncResult.delayUntil = (System.currentTimeMillis() / 1000) + 5 * 60
5047
return
5148
}
49+
5250
action == 0 -> {
5351
Xlog.d(TAG, "Database not changed, no need to sync")
5452
return
5553
}
54+
5655
action < 0 -> {
5756
Xlog.d(TAG, "Performing remote restore...")
5857
runBlocking { repository.gDriveRestoreSync() }
5958
}
59+
6060
else -> {
6161
Xlog.d(TAG, "Performing remote backup...")
6262
runBlocking { repository.gDriveBackupSync() }
@@ -86,8 +86,10 @@ class SyncAdapter(private val mContext: Context, autoInitialize: Boolean) :
8686
val databaseInfo = mContext.getDatabaseInfo(QuoteCollectionContract.DATABASE_NAME)
8787
return if (serverMarker.isNullOrEmpty() || syncTimestamp < 0 || databaseInfo.first.isNullOrEmpty()
8888
) {
89-
Xlog.d(TAG,
90-
"First sync or local database is not created, need to perform remote restore")
89+
Xlog.d(
90+
TAG,
91+
"First sync or local database is not created, need to perform remote restore"
92+
)
9193
null
9294
} else {
9395
when {
@@ -103,12 +105,6 @@ class SyncAdapter(private val mContext: Context, autoInitialize: Boolean) :
103105
const val SYNC_MARKER_KEY = BuildConfig.APPLICATION_ID + ".sync.marker"
104106
const val SYNC_TIMESTAMP_KEY = BuildConfig.APPLICATION_ID + ".sync.timestamp"
105107
}
106-
107-
@EntryPoint
108-
@InstallIn(SingletonComponent::class)
109-
interface SyncAdapterEntryPoint {
110-
fun collectionRepository(): QuoteCollectionRepository
111-
}
112108
}
113109

114110
/**

app/src/main/java/com/crossbowffs/quotelock/app/App.kt

+21-5
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,40 @@ import android.app.Application
44
import com.crossbowffs.quotelock.account.SyncAccountManager
55
import com.crossbowffs.quotelock.account.google.GoogleAccountHelper.getSignedInGoogleAccountEmail
66
import com.crossbowffs.quotelock.account.google.GoogleAccountHelper.isGoogleAccountSignedIn
7+
import com.crossbowffs.quotelock.di.DataModule
8+
import com.crossbowffs.quotelock.di.workerModule
79
import com.google.android.material.color.DynamicColors
8-
import dagger.hilt.android.HiltAndroidApp
9-
import javax.inject.Inject
10+
import org.koin.android.ext.android.inject
11+
import org.koin.android.ext.koin.androidContext
12+
import org.koin.android.ext.koin.androidLogger
13+
import org.koin.androidx.workmanager.koin.workManagerFactory
14+
import org.koin.core.context.startKoin
15+
import org.koin.ksp.generated.module
1016

1117
/**
1218
* @author Yubyf
1319
* @date 2021/6/20.
1420
*/
15-
@HiltAndroidApp
1621
class App : Application() {
1722

18-
@Inject
19-
lateinit var syncAccountManager: SyncAccountManager
23+
private val syncAccountManager: SyncAccountManager by inject()
2024

2125
override fun onCreate() {
2226
super.onCreate()
2327
DynamicColors.applyToActivitiesIfAvailable(this)
2428
instance = this
29+
30+
startKoin {
31+
// Log Koin into Android logger
32+
androidLogger()
33+
// Reference Android context
34+
androidContext(this@App)
35+
// Work factory
36+
workManagerFactory()
37+
// Load modules
38+
modules(DataModule().module, workerModule)
39+
}
40+
2541
syncAccountManager.initialize()
2642
if (isGoogleAccountSignedIn(this)) {
2743
getSignedInGoogleAccountEmail(this).takeIf { !it.isNullOrEmpty() }?.let { account ->

app/src/main/java/com/crossbowffs/quotelock/app/CommonReceiver.kt

+10-10
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,28 @@ import com.crossbowffs.quotelock.data.modules.QuoteRepository
88
import com.crossbowffs.quotelock.utils.WorkUtils
99
import com.crossbowffs.quotelock.utils.Xlog
1010
import com.crossbowffs.quotelock.utils.className
11-
import dagger.hilt.android.AndroidEntryPoint
12-
import javax.inject.Inject
11+
import org.koin.core.component.KoinComponent
12+
import org.koin.core.component.inject
1313

14-
@AndroidEntryPoint
15-
class CommonReceiver : BroadcastReceiver() {
16-
@Inject
17-
lateinit var quoteRepository: QuoteRepository
14+
class CommonReceiver : BroadcastReceiver(), KoinComponent {
1815

19-
@Inject
20-
lateinit var configurationRepository: ConfigurationRepository
16+
private val quoteRepository: QuoteRepository by inject()
17+
18+
private val configurationRepository: ConfigurationRepository by inject()
2119

2220
override fun onReceive(context: Context, intent: Intent) {
2321
val action = intent.action
2422
Xlog.d(TAG, "Received action: %s", action ?: "-")
2523
if (Intent.ACTION_BOOT_COMPLETED == action) {
2624
// Notify LockscreenHook to show current quotes after booting.
2725
quoteRepository.notifyBooted()
28-
WorkUtils.createQuoteDownloadWork(context,
26+
WorkUtils.createQuoteDownloadWork(
27+
context,
2928
configurationRepository.refreshInterval,
3029
configurationRepository.isRequireInternet,
3130
configurationRepository.isUnmeteredNetworkOnly,
32-
false)
31+
false
32+
)
3333
}
3434
}
3535

0 commit comments

Comments
 (0)