Skip to content

Commit

Permalink
refactor: Migrated from Hilt to Koin.
Browse files Browse the repository at this point in the history
* Hilt is making slow progress in adapting to KSP.(google/dagger#2349)
* Potential plans to support KMP in the future.
  • Loading branch information
Yubyf committed Jul 22, 2023
1 parent d840cd6 commit 87a1c0d
Show file tree
Hide file tree
Showing 91 changed files with 621 additions and 768 deletions.
11 changes: 5 additions & 6 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.android.kotlin)
alias(libs.plugins.serialization)
alias(libs.plugins.kapt)
alias(libs.plugins.ksp)
alias(libs.plugins.android.hilt)
}

//region Keystore
Expand Down Expand Up @@ -161,10 +159,9 @@ dependencies {
implementation(libs.bundles.androidx.room)
ksp(libs.androidx.room.compiler)

// Hilt
implementation(libs.bundles.hilt)
kapt(libs.hilt.compiler)
kaptAndroidTest(libs.hilt.compiler)
// Koin
implementation(libs.bundles.koin)
ksp(libs.koin.ksp.compiler)

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

// Test
testImplementation(libs.junit)
testImplementation(libs.bundles.koin.test)
androidTestImplementation(libs.bundles.androidx.test)
androidTestImplementation(libs.bundles.compose.test)
androidTestImplementation(libs.koin.android.test)
debugImplementation(libs.compose.test.manifest)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package com.crossbowffs.quotelock
import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import dagger.hilt.android.testing.HiltTestApplication
import com.crossbowffs.quotelock.app.TestApp

class CustomTestRunner : AndroidJUnitRunner() {

override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
return super.newApplication(cl, TestApp::class.java.name, context)
}
}
18 changes: 18 additions & 0 deletions app/src/androidTest/java/com/crossbowffs/quotelock/app/TestApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.crossbowffs.quotelock.app

import android.app.Application
import com.crossbowffs.quotelock.di.DataModule
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import org.koin.ksp.generated.module

class TestApp : Application() {

override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@TestApp)
modules(DataModule().module)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,18 @@
package com.crossbowffs.quotelock.data.modules.wikiquote

import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.crossbowffs.quotelock.utils.Xlog
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
import org.junit.runner.RunWith
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

@HiltAndroidTest
class WikiquoteRepositoryTest {
@RunWith(AndroidJUnit4::class)
class WikiquoteRepositoryTest : KoinComponent {

@get:Rule
var hiltRule = HiltAndroidRule(this)

@Inject
@ApplicationContext
lateinit var context: Context

@Inject
lateinit var repository: WikiquoteRepository

@Before
fun init() {
hiltRule.inject()
}
private val repository: WikiquoteRepository by inject()

@Test
fun testRequestAllQuotes() {
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,19 @@
android:resource="@xml/file_provider_paths" />
</provider>

<!-- For Koin.-->
<!-- See https://developer.android.com/topic/libraries/architecture/workmanager/advanced/custom-configuration#remove-default-->
<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>

<meta-data
android:name="xposedmodule"
android:value="true" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,30 @@ import com.crossbowffs.quotelock.account.syncadapter.getSyncTimestamp
import com.crossbowffs.quotelock.account.syncadapter.setServerSyncMarker
import com.crossbowffs.quotelock.data.modules.collections.QuoteCollectionRepository
import com.crossbowffs.quotelock.data.modules.collections.database.QuoteCollectionContract
import com.crossbowffs.quotelock.di.IoDispatcher
import com.crossbowffs.quotelock.di.DISPATCHER_IO
import com.crossbowffs.quotelock.utils.Xlog
import com.crossbowffs.quotelock.utils.className
import com.yubyf.quotelockx.BuildConfig
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
import org.koin.core.annotation.Named
import org.koin.core.annotation.Single

/**
* @author Yubyf
* @date 2021/6/20.
*/
class SyncAccountManager @Inject constructor(
@Single(createdAtStart = true)
class SyncAccountManager(
private val accountManager: AccountManager,
private val collectionRepository: QuoteCollectionRepository,
@IoDispatcher private val dispatcher: CoroutineDispatcher,
@Named(DISPATCHER_IO) private val dispatcher: CoroutineDispatcher,
) {

private val accountType: String
get() = BuildConfig.APPLICATION_ID + ".account"

fun initialize() {
CoroutineScope(dispatcher).launch {
collectionRepository.getAllStream().collect {
Expand All @@ -38,7 +43,8 @@ class SyncAccountManager @Inject constructor(
* changeUri is null.
*/
Xlog.d(TAG, "Data on change, requesting sync...")
ContentResolver.requestSync(currentSyncAccount,
ContentResolver.requestSync(
currentSyncAccount,
QuoteCollectionContract.AUTHORITY, Bundle()
)
}
Expand All @@ -57,7 +63,7 @@ class SyncAccountManager @Inject constructor(
clearAccountUserData(account)
}
} else {
account = Account(name, ACCOUNT_TYPE)
account = Account(name, accountType)
accountManager.addAccountExplicitly(account, null, null)
Xlog.d(TAG, "Added account of name $name")
clearAccountUserData(account)
Expand Down Expand Up @@ -110,11 +116,10 @@ class SyncAccountManager @Inject constructor(
}

private val currentSyncAccount: Account?
get() = accountManager.getAccountsByType(ACCOUNT_TYPE)
get() = accountManager.getAccountsByType(accountType)
.run { if (isNotEmpty()) this[0] else null }

companion object {
private val TAG = className<SyncAccountManager>()
private const val ACCOUNT_TYPE = BuildConfig.APPLICATION_ID + ".account"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import android.content.Context
import android.content.Intent
import com.crossbowffs.quotelock.data.api.GoogleAccount
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import org.koin.core.annotation.Single

/**
* @author Yubyf
*/
class GoogleAccountManager @Inject constructor(
@ApplicationContext private val context: Context,
@Single
class GoogleAccountManager(
private val context: Context,
) {

fun checkGooglePlayService(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,16 @@ import com.crossbowffs.quotelock.utils.Xlog
import com.crossbowffs.quotelock.utils.className
import com.crossbowffs.quotelock.utils.getDatabaseInfo
import com.yubyf.quotelockx.BuildConfig
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.runBlocking
import org.koin.core.component.KoinComponent
import org.koin.core.component.get

/**
* @author Yubyf
* @date 2021/6/20.
*/
class SyncAdapter(private val mContext: Context, autoInitialize: Boolean) :
AbstractThreadedSyncAdapter(mContext, autoInitialize) {
AbstractThreadedSyncAdapter(mContext, autoInitialize), KoinComponent {

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

Expand All @@ -40,23 +38,25 @@ class SyncAdapter(private val mContext: Context, autoInitialize: Boolean) :
syncResult.stats.numAuthExceptions++
return
}
val repository = EntryPointAccessors.fromApplication(
mContext.applicationContext, SyncAdapterEntryPoint::class.java).collectionRepository()
val repository: QuoteCollectionRepository = get()
val action = checkBackupOrRestore(account)
val result = when {
action == null -> {
Xlog.d(TAG, "No backup or restore action found. Retry 5 minutes later.")
syncResult.delayUntil = (System.currentTimeMillis() / 1000) + 5 * 60
return
}

action == 0 -> {
Xlog.d(TAG, "Database not changed, no need to sync")
return
}

action < 0 -> {
Xlog.d(TAG, "Performing remote restore...")
runBlocking { repository.gDriveRestoreSync() }
}

else -> {
Xlog.d(TAG, "Performing remote backup...")
runBlocking { repository.gDriveBackupSync() }
Expand Down Expand Up @@ -86,8 +86,10 @@ class SyncAdapter(private val mContext: Context, autoInitialize: Boolean) :
val databaseInfo = mContext.getDatabaseInfo(QuoteCollectionContract.DATABASE_NAME)
return if (serverMarker.isNullOrEmpty() || syncTimestamp < 0 || databaseInfo.first.isNullOrEmpty()
) {
Xlog.d(TAG,
"First sync or local database is not created, need to perform remote restore")
Xlog.d(
TAG,
"First sync or local database is not created, need to perform remote restore"
)
null
} else {
when {
Expand All @@ -103,12 +105,6 @@ class SyncAdapter(private val mContext: Context, autoInitialize: Boolean) :
const val SYNC_MARKER_KEY = BuildConfig.APPLICATION_ID + ".sync.marker"
const val SYNC_TIMESTAMP_KEY = BuildConfig.APPLICATION_ID + ".sync.timestamp"
}

@EntryPoint
@InstallIn(SingletonComponent::class)
interface SyncAdapterEntryPoint {
fun collectionRepository(): QuoteCollectionRepository
}
}

/**
Expand Down
26 changes: 21 additions & 5 deletions app/src/main/java/com/crossbowffs/quotelock/app/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,40 @@ import android.app.Application
import com.crossbowffs.quotelock.account.SyncAccountManager
import com.crossbowffs.quotelock.account.google.GoogleAccountHelper.getSignedInGoogleAccountEmail
import com.crossbowffs.quotelock.account.google.GoogleAccountHelper.isGoogleAccountSignedIn
import com.crossbowffs.quotelock.di.DataModule
import com.crossbowffs.quotelock.di.workerModule
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject
import org.koin.android.ext.android.inject
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.androidx.workmanager.koin.workManagerFactory
import org.koin.core.context.startKoin
import org.koin.ksp.generated.module

/**
* @author Yubyf
* @date 2021/6/20.
*/
@HiltAndroidApp
class App : Application() {

@Inject
lateinit var syncAccountManager: SyncAccountManager
private val syncAccountManager: SyncAccountManager by inject()

override fun onCreate() {
super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this)
instance = this

startKoin {
// Log Koin into Android logger
androidLogger()
// Reference Android context
androidContext(this@App)
// Work factory
workManagerFactory()
// Load modules
modules(DataModule().module, workerModule)
}

syncAccountManager.initialize()
if (isGoogleAccountSignedIn(this)) {
getSignedInGoogleAccountEmail(this).takeIf { !it.isNullOrEmpty() }?.let { account ->
Expand Down
20 changes: 10 additions & 10 deletions app/src/main/java/com/crossbowffs/quotelock/app/CommonReceiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,28 @@ import com.crossbowffs.quotelock.data.modules.QuoteRepository
import com.crossbowffs.quotelock.utils.WorkUtils
import com.crossbowffs.quotelock.utils.Xlog
import com.crossbowffs.quotelock.utils.className
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

@AndroidEntryPoint
class CommonReceiver : BroadcastReceiver() {
@Inject
lateinit var quoteRepository: QuoteRepository
class CommonReceiver : BroadcastReceiver(), KoinComponent {

@Inject
lateinit var configurationRepository: ConfigurationRepository
private val quoteRepository: QuoteRepository by inject()

private val configurationRepository: ConfigurationRepository by inject()

override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
Xlog.d(TAG, "Received action: %s", action ?: "-")
if (Intent.ACTION_BOOT_COMPLETED == action) {
// Notify LockscreenHook to show current quotes after booting.
quoteRepository.notifyBooted()
WorkUtils.createQuoteDownloadWork(context,
WorkUtils.createQuoteDownloadWork(
context,
configurationRepository.refreshInterval,
configurationRepository.isRequireInternet,
configurationRepository.isUnmeteredNetworkOnly,
false)
false
)
}
}

Expand Down
Loading

0 comments on commit 87a1c0d

Please sign in to comment.