diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..3f25798e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,2 @@
+[*.{kt,kts}]
+ktlint_code_style = android_studio
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 7829f3cf..aca9a1ab 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -6,8 +6,6 @@
-
-
diff --git a/build.gradle.kts b/build.gradle.kts
index 61856197..20c8a830 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -18,6 +18,7 @@ subprojects {
apply(plugin = rootProject.libs.plugins.ktlint.get().pluginId)
configure {
+ version.set("1.0.0")
enableExperimentalRules.set(true)
verbose.set(true)
filter {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 1828de35..a68d36cf 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,7 +8,7 @@ compileSdk = "34"
kotlin = "1.9.10"
android-gradle-plugin = "8.1.1"
-ktlint-gradle = "11.4.2"
+ktlint-gradle = "11.6.0"
compose = "1.5.2"
composeCompiler = "1.5.3"
diff --git a/shared/src/androidUnitTest/kotlin/co/touchlab/kampkit/KoinTest.kt b/shared/src/androidUnitTest/kotlin/co/touchlab/kampkit/KoinTest.kt
index 4be2ca84..ba8c2b8b 100644
--- a/shared/src/androidUnitTest/kotlin/co/touchlab/kampkit/KoinTest.kt
+++ b/shared/src/androidUnitTest/kotlin/co/touchlab/kampkit/KoinTest.kt
@@ -5,6 +5,8 @@ import android.content.Context
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import co.touchlab.kermit.Logger
+import kotlin.test.AfterTest
+import kotlin.test.Test
import org.junit.experimental.categories.Category
import org.junit.runner.RunWith
import org.koin.core.context.stopKoin
@@ -13,8 +15,6 @@ import org.koin.dsl.module
import org.koin.test.category.CheckModuleTest
import org.koin.test.check.checkModules
import org.robolectric.annotation.Config
-import kotlin.test.AfterTest
-import kotlin.test.Test
@RunWith(AndroidJUnit4::class)
@Category(CheckModuleTest::class)
diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/DatabaseHelper.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/DatabaseHelper.kt
index 66075ac0..6575c122 100644
--- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/DatabaseHelper.kt
+++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/DatabaseHelper.kt
@@ -19,12 +19,11 @@ class DatabaseHelper(
) {
private val dbRef: KaMPKitDb = KaMPKitDb(sqlDriver)
- fun selectAllItems(): Flow> =
- dbRef.tableQueries
- .selectAll()
- .asFlow()
- .mapToList(Dispatchers.Default)
- .flowOn(backgroundDispatcher)
+ fun selectAllItems(): Flow> = dbRef.tableQueries
+ .selectAll()
+ .asFlow()
+ .mapToList(Dispatchers.Default)
+ .flowOn(backgroundDispatcher)
suspend fun insertBreeds(breeds: List) {
log.d { "Inserting ${breeds.size} breeds into database" }
@@ -35,12 +34,11 @@ class DatabaseHelper(
}
}
- fun selectById(id: Long): Flow> =
- dbRef.tableQueries
- .selectById(id)
- .asFlow()
- .mapToList(Dispatchers.Default)
- .flowOn(backgroundDispatcher)
+ fun selectById(id: Long): Flow> = dbRef.tableQueries
+ .selectById(id)
+ .asFlow()
+ .mapToList(Dispatchers.Default)
+ .flowOn(backgroundDispatcher)
suspend fun deleteAll() {
log.i { "Database Cleared" }
diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/Koin.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/Koin.kt
index db7c2df1..f954ec0b 100644
--- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/Koin.kt
+++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/Koin.kt
@@ -62,7 +62,8 @@ private val coreModule = module {
// uses you *may* want to have a more robust configuration from the native platform. In KaMP Kit,
// that would likely go into platformModule expect/actual.
// See https://github.com/touchlab/Kermit
- val baseLogger = Logger(config = StaticConfig(logWriterList = listOf(platformLogWriter())), "KampKit")
+ val baseLogger =
+ Logger(config = StaticConfig(logWriterList = listOf(platformLogWriter())), "KampKit")
factory { (tag: String?) -> if (tag != null) baseLogger.withTag(tag) else baseLogger }
single {
diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ktor/DogApiImpl.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ktor/DogApiImpl.kt
index d82c2187..b4c4eeb7 100644
--- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ktor/DogApiImpl.kt
+++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ktor/DogApiImpl.kt
@@ -1,20 +1,20 @@
package co.touchlab.kampkit.ktor
import co.touchlab.kampkit.response.BreedResult
+import co.touchlab.kermit.Logger as KermitLogger
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.LogLevel
+import io.ktor.client.plugins.logging.Logger as KtorLogger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.get
import io.ktor.http.encodedPath
import io.ktor.http.takeFrom
import io.ktor.serialization.kotlinx.json.json
-import co.touchlab.kermit.Logger as KermitLogger
-import io.ktor.client.plugins.logging.Logger as KtorLogger
class DogApiImpl(private val log: KermitLogger, engine: HttpClientEngine) : DogApi {
diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/models/BreedViewModel.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/models/BreedViewModel.kt
index 16467436..5dffe451 100644
--- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/models/BreedViewModel.kt
+++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/models/BreedViewModel.kt
@@ -95,10 +95,14 @@ class BreedViewModel(
log.e(throwable) { "Error downloading breed list" }
mutableBreedState.update {
when (it) {
- is BreedViewState.Content -> it.copy(isLoading = false) // Just let it fail silently if we have a cache
+ is BreedViewState.Content -> it.copy(
+ isLoading = false
+ ) // Just let it fail silently if we have a cache
is BreedViewState.Empty,
is BreedViewState.Error,
- is BreedViewState.Initial -> BreedViewState.Error(error = "Unable to refresh breed list")
+ is BreedViewState.Initial -> BreedViewState.Error(
+ error = "Unable to refresh breed list"
+ )
}
}
}
diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/sqldelight/CoroutinesExtensions.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/sqldelight/CoroutinesExtensions.kt
index 12fe6c71..b383cc0e 100644
--- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/sqldelight/CoroutinesExtensions.kt
+++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/sqldelight/CoroutinesExtensions.kt
@@ -2,8 +2,8 @@ package co.touchlab.kampkit.sqldelight
import app.cash.sqldelight.Transacter
import app.cash.sqldelight.TransactionWithoutReturn
-import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
suspend fun Transacter.transactionWithContext(
coroutineContext: CoroutineContext,
diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt
index 9656b8d3..da9a59d5 100644
--- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt
+++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt
@@ -8,14 +8,14 @@ import co.touchlab.kampkit.models.BreedRepository
import co.touchlab.kermit.Logger
import co.touchlab.kermit.StaticConfig
import com.russhwolf.settings.MapSettings
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.test.runTest
-import kotlinx.datetime.Clock
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails
import kotlin.time.Duration.Companion.hours
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.test.runTest
+import kotlinx.datetime.Clock
class BreedRepositoryTest {
@@ -32,7 +32,8 @@ class BreedRepositoryTest {
// Need to start at non-zero time because the default value for db timestamp is 0
private val clock = ClockMock(Clock.System.now())
- private val repository: BreedRepository = BreedRepository(dbHelper, settings, ktorApi, kermit, clock)
+ private val repository: BreedRepository =
+ BreedRepository(dbHelper, settings, ktorApi, kermit, clock)
companion object {
private val appenzeller = Breed(1, "appenzeller", false)
@@ -60,7 +61,9 @@ class BreedRepositoryTest {
@Test
fun `Get updated breeds with cache and preserve favorites`() = runTest {
val successResult = ktorApi.successResult()
- val resultWithExtraBreed = successResult.copy(message = successResult.message + ("extra" to emptyList()))
+ val resultWithExtraBreed = successResult.copy(
+ message = successResult.message + ("extra" to emptyList())
+ )
ktorApi.prepareResult(resultWithExtraBreed)
dbHelper.insertBreeds(breedNames)
@@ -78,10 +81,15 @@ class BreedRepositoryTest {
@Test
fun `Get updated breeds when stale and preserve favorites`() = runTest {
- settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds())
+ settings.putLong(
+ BreedRepository.DB_TIMESTAMP_KEY,
+ (clock.currentInstant - 2.hours).toEpochMilliseconds()
+ )
val successResult = ktorApi.successResult()
- val resultWithExtraBreed = successResult.copy(message = successResult.message + ("extra" to emptyList()))
+ val resultWithExtraBreed = successResult.copy(
+ message = successResult.message + ("extra" to emptyList())
+ )
ktorApi.prepareResult(resultWithExtraBreed)
dbHelper.insertBreeds(breedNames)
@@ -110,7 +118,10 @@ class BreedRepositoryTest {
@Test
fun `No web call if data is not stale`() = runTest {
- settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds())
+ settings.putLong(
+ BreedRepository.DB_TIMESTAMP_KEY,
+ clock.currentInstant.toEpochMilliseconds()
+ )
ktorApi.prepareResult(ktorApi.successResult())
repository.refreshBreedsIfStale()
@@ -132,7 +143,10 @@ class BreedRepositoryTest {
@Test
fun `Rethrow on API error when stale`() = runTest {
- settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds())
+ settings.putLong(
+ BreedRepository.DB_TIMESTAMP_KEY,
+ (clock.currentInstant - 2.hours).toEpochMilliseconds()
+ )
ktorApi.throwOnCall(RuntimeException("Test error"))
val throwable = assertFails {
diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedViewModelTest.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedViewModelTest.kt
index 6dcc9021..55a2ddcb 100644
--- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedViewModelTest.kt
+++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedViewModelTest.kt
@@ -12,6 +12,11 @@ import co.touchlab.kampkit.response.BreedResult
import co.touchlab.kermit.Logger
import co.touchlab.kermit.StaticConfig
import com.russhwolf.settings.MapSettings
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.time.Duration.Companion.hours
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@@ -19,11 +24,6 @@ import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import kotlinx.datetime.Clock
-import kotlin.test.AfterTest
-import kotlin.test.BeforeTest
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.time.Duration.Companion.hours
class BreedViewModelTest {
private var kermit = Logger(StaticConfig())
@@ -39,7 +39,8 @@ class BreedViewModelTest {
// Need to start at non-zero time because the default value for db timestamp is 0
private val clock = ClockMock(Clock.System.now())
- private val repository: BreedRepository = BreedRepository(dbHelper, settings, ktorApi, kermit, clock)
+ private val repository: BreedRepository =
+ BreedRepository(dbHelper, settings, ktorApi, kermit, clock)
private val viewModel by lazy {
BreedViewModel(repository, kermit)
@@ -96,10 +97,15 @@ class BreedViewModelTest {
@Test
fun `Get updated breeds with cache and preserve favorites`() = runTest {
- settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds())
+ settings.putLong(
+ BreedRepository.DB_TIMESTAMP_KEY,
+ clock.currentInstant.toEpochMilliseconds()
+ )
val successResult = ktorApi.successResult()
- val resultWithExtraBreed = successResult.copy(message = successResult.message + ("extra" to emptyList()))
+ val resultWithExtraBreed = successResult.copy(
+ message = successResult.message + ("extra" to emptyList())
+ )
ktorApi.prepareResult(resultWithExtraBreed)
dbHelper.insertBreeds(breedNames)
@@ -112,7 +118,9 @@ class BreedViewModelTest {
viewModel.refreshBreeds()
// id is 5 here because it incremented twice when trying to insert duplicate breeds
assertEquals(
- BreedViewState.Content(breedViewStateSuccessFavorite.breeds + Breed(5, "extra", false)),
+ BreedViewState.Content(
+ breedViewStateSuccessFavorite.breeds + Breed(5, "extra", false)
+ ),
awaitItemPrecededBy(breedViewStateSuccessFavorite.copy(isLoading = true))
)
}
@@ -120,10 +128,15 @@ class BreedViewModelTest {
@Test
fun `Get updated breeds when stale and preserve favorites`() = runTest {
- settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds())
+ settings.putLong(
+ BreedRepository.DB_TIMESTAMP_KEY,
+ (clock.currentInstant - 2.hours).toEpochMilliseconds()
+ )
val successResult = ktorApi.successResult()
- val resultWithExtraBreed = successResult.copy(message = successResult.message + ("extra" to emptyList()))
+ val resultWithExtraBreed = successResult.copy(
+ message = successResult.message + ("extra" to emptyList())
+ )
ktorApi.prepareResult(resultWithExtraBreed)
dbHelper.insertBreeds(breedNames)
@@ -132,7 +145,9 @@ class BreedViewModelTest {
viewModel.breedState.test {
// id is 5 here because it incremented twice when trying to insert duplicate breeds
assertEquals(
- BreedViewState.Content(breedViewStateSuccessFavorite.breeds + Breed(5, "extra", false)),
+ BreedViewState.Content(
+ breedViewStateSuccessFavorite.breeds + Breed(5, "extra", false)
+ ),
awaitItemPrecededBy(BreedViewState.Initial, breedViewStateSuccessFavorite)
)
}
@@ -140,7 +155,10 @@ class BreedViewModelTest {
@Test
fun `Toggle favorite cached breed`() = runTest {
- settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds())
+ settings.putLong(
+ BreedRepository.DB_TIMESTAMP_KEY,
+ clock.currentInstant.toEpochMilliseconds()
+ )
dbHelper.insertBreeds(breedNames)
dbHelper.updateFavorite(australianLike.id, true)
@@ -159,12 +177,18 @@ class BreedViewModelTest {
@Test
fun `No web call if data is not stale`() = runTest {
- settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds())
+ settings.putLong(
+ BreedRepository.DB_TIMESTAMP_KEY,
+ clock.currentInstant.toEpochMilliseconds()
+ )
ktorApi.prepareResult(ktorApi.successResult())
dbHelper.insertBreeds(breedNames)
viewModel.breedState.test {
- assertEquals(breedViewStateSuccessNoFavorite, awaitItemPrecededBy(BreedViewState.Initial))
+ assertEquals(
+ breedViewStateSuccessNoFavorite,
+ awaitItemPrecededBy(BreedViewState.Initial)
+ )
assertEquals(0, ktorApi.calledCount)
expectNoEvents()
@@ -192,7 +216,10 @@ class BreedViewModelTest {
@Test
fun `Ignore API error with cache`() = runTest {
dbHelper.insertBreeds(breedNames)
- settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds())
+ settings.putLong(
+ BreedRepository.DB_TIMESTAMP_KEY,
+ (clock.currentInstant - 2.hours).toEpochMilliseconds()
+ )
ktorApi.throwOnCall(RuntimeException("Test error"))
viewModel.breedState.test {
@@ -235,7 +262,10 @@ class BreedViewModelTest {
@Test
fun `Show API error on refresh without cache`() = runTest {
- settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds())
+ settings.putLong(
+ BreedRepository.DB_TIMESTAMP_KEY,
+ clock.currentInstant.toEpochMilliseconds()
+ )
ktorApi.throwOnCall(RuntimeException("Test error"))
viewModel.breedState.test {
@@ -253,7 +283,9 @@ class BreedViewModelTest {
// There's a race condition where intermediate states can get missed if the next state comes too fast.
// This function addresses that by awaiting an item that may or may not be preceded by the specified other items
-private suspend fun ReceiveTurbine.awaitItemPrecededBy(vararg items: BreedViewState): BreedViewState {
+private suspend fun ReceiveTurbine.awaitItemPrecededBy(
+ vararg items: BreedViewState
+): BreedViewState {
var nextItem = awaitItem()
for (item in items) {
if (item == nextItem) {
diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/DogApiTest.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/DogApiTest.kt
index 77998e35..d390d87b 100644
--- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/DogApiTest.kt
+++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/DogApiTest.kt
@@ -13,10 +13,10 @@ import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.headersOf
-import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
+import kotlinx.coroutines.test.runTest
class DogApiTest {
private val emptyLogger = Logger(
@@ -32,8 +32,13 @@ class DogApiTest {
val engine = MockEngine {
assertEquals("https://dog.ceo/api/breeds/list/all", it.url.toString())
respond(
- content = """{"message":{"affenpinscher":[],"african":["shepherd"]},"status":"success"}""",
- headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ content = """
+ {"message":{"affenpinscher":[],"african":["shepherd"]},"status":"success"}
+ """.trimIndent(),
+ headers = headersOf(
+ HttpHeaders.ContentType,
+ ContentType.Application.Json.toString()
+ )
)
}
val dogApi = DogApiImpl(emptyLogger, engine)
diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/SqlDelightTest.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/SqlDelightTest.kt
index 1bb86861..609a8f8e 100644
--- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/SqlDelightTest.kt
+++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/SqlDelightTest.kt
@@ -2,13 +2,13 @@ package co.touchlab.kampkit
import co.touchlab.kermit.Logger
import co.touchlab.kermit.StaticConfig
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.runTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runTest
class SqlDelightTest {
diff --git a/shared/src/iosMain/kotlin/co/touchlab/kampkit/KoinIOS.kt b/shared/src/iosMain/kotlin/co/touchlab/kampkit/KoinIOS.kt
index 5dd611ee..148244db 100644
--- a/shared/src/iosMain/kotlin/co/touchlab/kampkit/KoinIOS.kt
+++ b/shared/src/iosMain/kotlin/co/touchlab/kampkit/KoinIOS.kt
@@ -37,8 +37,7 @@ actual val platformModule = module {
// Access from Swift to create a logger
@Suppress("unused")
-fun Koin.loggerWithTag(tag: String) =
- get(qualifier = null) { parametersOf(tag) }
+fun Koin.loggerWithTag(tag: String) = get(qualifier = null) { parametersOf(tag) }
@Suppress("unused") // Called from Swift
object KotlinDependencies : KoinComponent {
diff --git a/shared/src/iosTest/kotlin/co/touchlab/kampkit/KoinTest.kt b/shared/src/iosTest/kotlin/co/touchlab/kampkit/KoinTest.kt
index 99cf751d..834b8dfc 100644
--- a/shared/src/iosTest/kotlin/co/touchlab/kampkit/KoinTest.kt
+++ b/shared/src/iosTest/kotlin/co/touchlab/kampkit/KoinTest.kt
@@ -1,12 +1,12 @@
package co.touchlab.kampkit
import co.touchlab.kermit.Logger
+import kotlin.test.AfterTest
+import kotlin.test.Test
import org.koin.core.context.stopKoin
import org.koin.core.parameter.parametersOf
import org.koin.test.check.checkModules
import platform.Foundation.NSUserDefaults
-import kotlin.test.AfterTest
-import kotlin.test.Test
class KoinTest {
@Test