Skip to content

Commit

Permalink
feat: migrate room to sqldelight for multiplatform database (#455)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: renamed database to `musicsearch.db`, changed some order, default values, and constraints
  • Loading branch information
David Ly committed Oct 1, 2023
1 parent a7b66a9 commit 8485f42
Show file tree
Hide file tree
Showing 505 changed files with 8,910 additions and 38,840 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,5 @@ jobs:
- name: Verify screenshot tests
run: ./gradlew verifyPaparazziDebug

# TODO: Neither of these actually test code from their own module, so we're getting 0%
- name: Unit test Android modules with coverage
run: ./gradlew app:createDebugUnitTestCoverageReport data-android:createDebugUnitTestCoverageReport
run: ./gradlew app:createDebugUnitTestCoverageReport
2 changes: 2 additions & 0 deletions .idea/detekt.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ See [PRIVACY_POLICY.md](PRIVACY_POLICY.md)

## Database schema

![](assets/mbjc_db_schema.svg)
![](assets/musicsearch_db_schema.svg)
5 changes: 4 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ android {

dependencies {
implementation(projects.dataAndroid)
implementation(projects.data.database)
implementation(projects.ui.common)
implementation(projects.ui.core)
implementation(projects.ui.collections)
Expand Down Expand Up @@ -102,6 +103,7 @@ dependencies {
implementation(libs.koin.androidx.compose)
implementation(libs.koin.annotations)
implementation(libs.koin.core)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.timber)

debugImplementation(libs.compose.ui.tooling)
Expand All @@ -120,7 +122,8 @@ dependencies {
androidTestImplementation(libs.coil.test)
androidTestImplementation(libs.compose.ui.test)
androidTestImplementation(libs.koin.test)
androidTestImplementation(libs.room.testing)
androidTestImplementation(libs.sqldelight.android.driver)
androidTestImplementation(libs.test.parameter.injector)

ksp(libs.koin.ksp.compiler)
kspAndroidTest(libs.koin.ksp.compiler)
Expand Down
12 changes: 0 additions & 12 deletions app/config/baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,9 @@
<ID>ComposableParamOrder:SeriesStatsScreen.kt$SeriesStatsScreen</ID>
<ID>ComposableParamOrder:WorkDetailsScreen.kt$WorkDetailsScreen</ID>
<ID>ComposableParamOrder:WorkStatsScreen.kt$WorkGroupStatsScreen</ID>
<ID>LongParameterList:TopLevelViewModel.kt$TopLevelViewModel$( val appPreferences: AppPreferences, private val musicBrainzOAuthInfo: MusicBrainzOAuthInfo, private val collectionDao: CollectionDao, private val collectionEntityDao: CollectionEntityDao, private val musicBrainzApi: MusicBrainzApi, private val musicBrainzAuthStore: MusicBrainzAuthStore, private val authRequest: AuthorizationRequest, private val authService: AuthorizationService, private val clientAuth: ClientAuthentication, private val lookupHistoryRepository: LookupHistoryRepository, )</ID>
<ID>MaxLineLength:InstrumentDetailsScreen.kt$description</ID>
<ID>MaxLineLength:RecordingsByWorkViewModel.kt$RecordingsByWorkViewModel$)</ID>
<ID>ModifierWithoutDefault:ReleaseGroupsByArtistScreen.kt$modifier</ID>
<ID>TooGenericExceptionCaught:TopLevelViewModel.kt$TopLevelViewModel$ex: Exception</ID>
<ID>UnstableCollections:AreaStatsScreen.kt$List&lt;Tab&gt;</ID>
<ID>UnstableCollections:ArtistStatsScreen.kt$List&lt;Tab&gt;</ID>
<ID>UnstableCollections:EventStatsScreen.kt$List&lt;Tab&gt;</ID>
<ID>UnstableCollections:InstrumentStatsScreen.kt$List&lt;Tab&gt;</ID>
<ID>UnstableCollections:LabelStatsScreen.kt$List&lt;Tab&gt;</ID>
<ID>UnstableCollections:PlaceStatsScreen.kt$List&lt;Tab&gt;</ID>
<ID>UnstableCollections:RecordingStatsScreen.kt$List&lt;Tab&gt;</ID>
<ID>UnstableCollections:ReleaseGroupStatsScreen.kt$List&lt;Tab&gt;</ID>
<ID>UnstableCollections:ReleaseStatsScreen.kt$List&lt;Tab&gt;</ID>
<ID>UnstableCollections:SeriesStatsScreen.kt$List&lt;Tab&gt;</ID>
<ID>UnstableCollections:WorkStatsScreen.kt$List&lt;Tab&gt;</ID>
</CurrentIssues>
</SmellBaseline>
21 changes: 21 additions & 0 deletions app/src/androidTest/java/ly/david/mbjc/KoinTestRule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ly.david.mbjc

import androidx.test.platform.app.InstrumentationRegistry
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin

class KoinTestRule : TestWatcher() {
override fun starting(description: Description) {
startKoin {
androidContext(InstrumentationRegistry.getInstrumentation().targetContext.applicationContext)
modules(testAndroidAppModule)
}
}

override fun finished(description: Description) {
stopKoin()
}
}
6 changes: 1 addition & 5 deletions app/src/androidTest/java/ly/david/mbjc/MainActivityTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,14 @@ import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import ly.david.data.room.MusicSearchRoomDatabase
import ly.david.mbjc.ui.MainActivity
import org.junit.Rule
import org.koin.test.KoinTest
import org.koin.test.inject

internal abstract class MainActivityTest : KoinTest {

private val database: MusicSearchRoomDatabase by inject()

@get:Rule(order = 0)
val musicSearchRoomDatabaseTestRule = MusicSearchRoomDatabaseTestRule(database)
val koinTestRule = KoinTestRule()

@get:Rule(order = 1)
val instantTaskExecutorRule = InstantTaskExecutorRule()
Expand Down

This file was deleted.

10 changes: 6 additions & 4 deletions app/src/androidTest/java/ly/david/mbjc/TestAndroidAppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import ly.david.data.di.auth.AuthStoreModule
import ly.david.data.di.coroutines.coroutinesScopesModule
import ly.david.data.di.logging.loggingModule
import ly.david.data.di.musicbrainz.musicBrainzAuthModule
import ly.david.data.di.room.databaseDaoModule
import ly.david.data.domain.DomainDataModule
import ly.david.data.musicbrainz.auth.MusicBrainzDataModule
import ly.david.data.spotify.di.SpotifyDataModule
import ly.david.mbjc.di.appDataModule
import ly.david.mbjc.di.testCoroutineDispatchersModule
import ly.david.mbjc.di.testDatabaseModule
import ly.david.mbjc.di.testDatabaseDriverModule
import ly.david.mbjc.di.testImageModule
import ly.david.mbjc.di.testNetworkModule
import ly.david.mbjc.di.testPreferencesDataStoreModule
import ly.david.musicsearch.data.database.databaseDaoModule
import ly.david.musicsearch.data.database.databaseModule
import ly.david.ui.collections.CollectionUiModule
import ly.david.ui.common.CommonUiModule
import ly.david.ui.history.HistoryUiModule
Expand All @@ -28,15 +29,14 @@ val testAndroidAppModule = module {
testCoroutineDispatchersModule,
testNetworkModule,
testPreferencesDataStoreModule,
testDatabaseModule,
testImageModule,
testDatabaseDriverModule,

ViewModelsModule().module,
appDataModule,
coroutinesScopesModule,
loggingModule,
musicBrainzAuthModule,
databaseDaoModule,
CoverArtDataModule().module,
DomainDataModule().module,
MusicBrainzDataModule().module,
Expand All @@ -47,5 +47,7 @@ val testAndroidAppModule = module {
HistoryUiModule().module,
NowPlayingUiModule().module,
SettingsUiModule().module,
databaseModule,
databaseDaoModule,
)
}
18 changes: 1 addition & 17 deletions app/src/androidTest/java/ly/david/mbjc/TestApplication.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
package ly.david.mbjc

import android.app.Application
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin

class TestApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@TestApplication)
modules(testAndroidAppModule)
}
}

override fun onTerminate() {
super.onTerminate()
stopKoin()
}
}
class TestApplication : Application()
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
package ly.david.mbjc.di

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import ly.david.data.di.coroutines.MusicSearchDispatchers
import org.koin.core.qualifier.named
import ly.david.data.core.CoroutineDispatchers
import org.koin.dsl.module

@OptIn(ExperimentalCoroutinesApi::class)
val testDispatcherModule = module {
single {
UnconfinedTestDispatcher()
}
}

val testCoroutineDispatchersModule = module {
includes(testDispatcherModule)
factory<CoroutineDispatcher>(named(MusicSearchDispatchers.Default)) {
get<TestDispatcher>()
}
factory<CoroutineDispatcher>(named(MusicSearchDispatchers.IO)) {
get<TestDispatcher>()
factory {
val testDispatcher = UnconfinedTestDispatcher()
CoroutineDispatchers(
default = testDispatcher,
io = testDispatcher,
)
}
}
30 changes: 17 additions & 13 deletions app/src/androidTest/java/ly/david/mbjc/di/TestDatabaseModule.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
package ly.david.mbjc.di

import androidx.room.Room
import ly.david.data.room.MusicSearchDatabase
import ly.david.data.room.MusicSearchRoomDatabase
import org.koin.dsl.binds
import androidx.sqlite.db.SupportSQLiteDatabase
import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
import ly.david.musicsearch.data.database.Database
import org.koin.dsl.module

// TODO: dupe
val testDatabaseModule = module {
single {
Room.inMemoryDatabaseBuilder(
get(),
MusicSearchRoomDatabase::class.java
val testDatabaseDriverModule = module {
single<SqlDriver> {
AndroidSqliteDriver(
schema = Database.Schema,
context = get(),
name = null,
callback = object : AndroidSqliteDriver.Callback(Database.Schema) {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onConfigure(db)
db.setForeignKeyConstraintsEnabled(true)
}
}
)
.allowMainThreadQueries()
.build()
} binds arrayOf(MusicSearchDatabase::class, MusicSearchRoomDatabase::class)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.preferencesDataStoreFile
import java.util.Random
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import ly.david.data.di.coroutines.MusicSearchDispatchers
import org.koin.core.qualifier.named
import ly.david.data.core.CoroutineDispatchers
import org.koin.dsl.module

private const val TEST_SETTINGS_KEY = "test_settings"
Expand All @@ -24,7 +22,7 @@ val testPreferencesDataStoreModule = module {
produceNewData = { emptyPreferences() }
),
migrations = listOf(SharedPreferencesMigration(get(), TEST_SETTINGS_KEY)),
scope = CoroutineScope(SupervisorJob() + get<CoroutineDispatcher>(named(MusicSearchDispatchers.IO))),
scope = CoroutineScope(SupervisorJob() + get<CoroutineDispatchers>().io),
produceFile = { get<Context>().preferencesDataStoreFile("${TEST_SETTINGS_KEY}_$random") }
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import ly.david.mbjc.StringReferences
import ly.david.ui.common.topappbar.TopAppBarWithFilterTestTag
import ly.david.ui.core.theme.PreviewTheme
import org.junit.Test
import org.koin.test.KoinTest
import org.koin.test.inject

/**
Expand All @@ -34,7 +33,7 @@ import org.koin.test.inject
* However, try to refrain from testing the details of constituent composables such as its cards.
* These should be tested in its own test class (screenshot tests). For now, previews will be enough.
*/
internal class AreaScaffoldTest : MainActivityTest(), StringReferences, KoinTest {
internal class AreaScaffoldTest : MainActivityTest(), StringReferences {

private val areaRepository: AreaRepository by inject()

Expand Down Expand Up @@ -79,9 +78,8 @@ internal class AreaScaffoldTest : MainActivityTest(), StringReferences, KoinTest
setArea(ontario)

waitForThenPerformClickOn(relationships)
// TODO: passes locally but not in CI
// waitForThenAssertIsDisplayed(canada.name)
// waitForThenAssertIsDisplayed(toronto.name)
waitForThenAssertIsDisplayed(canada.name)
waitForThenAssertIsDisplayed(toronto.name)

composeTestRule
.onNodeWithContentDescription(filter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import coil.Coil
import coil.ImageLoaderFactory
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.test.runTest
import ly.david.data.core.getDisplayNames
import ly.david.data.core.artist.getDisplayNames
import ly.david.data.core.getNameWithDisambiguation
import ly.david.data.domain.artist.ArtistRepository
import ly.david.data.musicbrainz.ArtistMusicBrainzModel
Expand All @@ -34,10 +34,9 @@ import ly.david.ui.common.topappbar.TopAppBarWithFilterTestTag
import ly.david.ui.core.theme.PreviewTheme
import org.junit.Before
import org.junit.Test
import org.koin.test.KoinTest
import org.koin.test.inject

internal class ArtistScaffoldTest : MainActivityTest(), StringReferences, KoinTest {
internal class ArtistScaffoldTest : MainActivityTest(), StringReferences {

private val artistRepository: ArtistRepository by inject()
private val imageLoaderFactory: ImageLoaderFactory by inject()
Expand Down
Loading

0 comments on commit 8485f42

Please sign in to comment.