From 8f79608f5d6cea84d9b72d46746eee631796ed69 Mon Sep 17 00:00:00 2001 From: Adrian Kuta Date: Wed, 10 Jun 2026 16:54:02 +0200 Subject: [PATCH] REDI-101: replace em/en dashes with hyphens in prose & comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Em dashes are a common AI-writing tell; swap them (and en dashes) for plain hyphens across the README and all KDoc/comment prose so the repo reads as hand-authored. Byte-level replace of U+2014/U+2013 -> '-'; arrows and the ellipsis are left untouched. The two functional em dashes are intentionally kept: the `DASH = "—"` blank-field UI placeholder in CharacterDetailUi and the preview sample that mirrors it -- those are deliberate UX, not prose. --- .gitignore | 2 +- README.md | 64 +++++++++---------- app/build.gradle.kts | 4 +- .../architecture/CharactersViewsRoute.kt | 2 +- .../AndroidUnitTestConventionPlugin.kt | 2 +- .../convention/ComposeConventionPlugin.kt | 2 +- .../core/data/di/CoreDataModule.kt | 2 +- .../core/design/system/theme/Color.kt | 2 +- .../architecture/core/domain/Result.kt | 2 +- .../about/presentation/AboutNavigation.kt | 2 +- .../feature/about/presentation/AboutScreen.kt | 4 +- .../about/presentation/AboutViewModel.kt | 6 +- .../datasource/KtorCharacterDataSource.kt | 2 +- .../data/NetworkCharacterRepositoryTest.kt | 2 +- .../usecase/GetCharactersPageUseCase.kt | 4 +- .../usecase/GetCharactersPageUseCaseTest.kt | 2 +- .../compose/CharacterListRobot.kt | 2 +- .../compose/CharacterDetailScreen.kt | 2 +- .../compose/CharacterListScreen.kt | 2 +- .../compose/CharactersNavigation.kt | 4 +- .../presentation/compose/ErrorDemoScreen.kt | 2 +- .../views/CharacterListFragment.kt | 2 +- .../views/CharacterStatusViews.kt | 2 +- .../characters/presentation/build.gradle.kts | 2 +- .../presentation/CharacterDetailViewModel.kt | 2 +- .../presentation/CharacterListState.kt | 2 +- .../presentation/CharacterListViewModel.kt | 4 +- .../presentation/ErrorDemoAction.kt | 2 +- .../characters/presentation/ErrorDemoState.kt | 4 +- .../presentation/ErrorDemoViewModel.kt | 6 +- .../di/CharactersPresentationModule.kt | 2 +- .../presentation/model/CharacterUi.kt | 2 +- .../CharacterDetailViewModelTest.kt | 4 +- .../CharacterListViewModelTest.kt | 4 +- gradle/libs.versions.toml | 4 +- 35 files changed, 79 insertions(+), 79 deletions(-) diff --git a/.gitignore b/.gitignore index a5d6b8e..d388411 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,5 @@ # Local config / secrets local.properties -# IDE (JetBrains / Android Studio) — fully ignored to avoid machine-specific churn +# IDE (JetBrains / Android Studio) - fully ignored to avoid machine-specific churn /.idea/ diff --git a/README.md b/README.md index 17fd293..938480d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Android Architecture Showcase A single, runnable **Android-only (Jetpack Compose)** reference app that demonstrates a complete, -idiomatic multi-module architecture — each convention shown in its own minimal-but-complete module. +idiomatic multi-module architecture - each convention shown in its own minimal-but-complete module. It is a teaching repo: the goal is not features but *how the pieces fit together*. Data comes from the no-key [Rick & Morty API](https://rickandmortyapi.com/). The app lists @@ -81,12 +81,12 @@ Features never depend on each other; anything shared moves to a `core` module; ` |---|---| | `presentation` | own `domain`, `core:domain`, `core:presentation`, `core:design-system` | | `data` | own `domain`, `core:domain`, `core:data` | -| `domain` | `core:domain` only — never `data` or `presentation` | +| `domain` | `core:domain` only - never `data` or `presentation` | | `:app` | everything | A key consequence: `:core:presentation`'s `UiText` is **Compose-free**, and the `compose` convention uses `implementation` (not `api`), so the UI-agnostic `:feature:characters:presentation` never gets -Compose on its classpath — which is what lets two different renderers share one ViewModel. +Compose on its classpath - which is what lets two different renderers share one ViewModel. --- @@ -98,19 +98,19 @@ One request flows through every layer, each with one job: Rick & Morty API │ JSON ▼ -CharacterDto / CharactersResponseDto (:data/dto) – serialization shape - │ CharacterMapper.toDomain() (:data/mappers) – DTO → domain, never the reverse leaks up +CharacterDto / CharactersResponseDto (:data/dto) - serialization shape + │ CharacterMapper.toDomain() (:data/mappers) - DTO → domain, never the reverse leaks up ▼ -Character / CharactersPage (:domain/model) – pure Kotlin domain model +Character / CharactersPage (:domain/model) - pure Kotlin domain model │ CharacterRepository.getCharacters() (:domain contract, :data impl) - │ GetCharactersPageUseCase(page) (:domain/usecase) – domain operation (see note) + │ GetCharactersPageUseCase(page) (:domain/usecase) - domain operation (see note) ▼ -CharacterListViewModel (:presentation) – holds State, processes Action, emits Event - │ Character.toCharacterUi() (:presentation/model)– domain → UI model (display shaping) +CharacterListViewModel (:presentation) - holds State, processes Action, emits Event + │ Character.toCharacterUi() (:presentation/model)- domain → UI model (display shaping) ▼ -CharacterUi in CharacterListState (:presentation) – immutable UI state +CharacterUi in CharacterListState (:presentation) - immutable UI state ▼ -CharacterListScreen / CharacterListFragment (:presentation-compose / -views) – dumb renderers +CharacterListScreen / CharacterListFragment (:presentation-compose / -views) - dumb renderers ``` - **DTOs** (`*Dto`) live in `data`; **domain models** are separate and never become DTOs/entities. @@ -118,16 +118,16 @@ CharacterListScreen / CharacterListFragment (:presentation-compose / -views) - **UI models** (`*Ui`) live in `presentation` and carry display-ready data (e.g. blank detail fields pre-formatted to an em dash). -### Note — when to add a UseCase +### Note - when to add a UseCase `GetCharactersPageUseCase` is intentionally a **thin pass-through** included to show the convention. The rule it illustrates: -> Add a UseCase when a screen needs **business logic that doesn't belong in the ViewModel** — real +> Add a UseCase when a screen needs **business logic that doesn't belong in the ViewModel** - real > rules, or **composition of several repositories/sources** into one operation. When the ViewModel > would merely forward a single repository call, injecting the repository directly is fine. -Here the list VM uses the UseCase; the detail VM calls `CharacterRepository` directly — both are +Here the list VM uses the UseCase; the detail VM calls `CharacterRepository` directly - both are correct, and the contrast is the point. --- @@ -140,39 +140,39 @@ Both patterns live side by side so the trade-off is concrete. |---|---|---| | State | one immutable `State` data class | one immutable `State` data class | | User input | a single `onAction(Action)` funnel + sealed `Action` | plain public methods on the `ViewModel` | -| Side effects | one-time `Event`s via a `Channel` (nav, snackbar) | none — the screen calls a method / uses `LocalUriHandler` | +| Side effects | one-time `Event`s via a `Channel` (nav, snackbar) | none - the screen calls a method / uses `LocalUriHandler` | | Best when | state is complex and interacting; effects matter | the screen is small and mostly static | -The flagship list is **MVI** because its state is genuinely complex — pagination, loading vs. -next-page loading, error surfacing, `SavedStateHandle` restore after process death — and it emits +The flagship list is **MVI** because its state is genuinely complex - pagination, loading vs. +next-page loading, error surfacing, `SavedStateHandle` restore after process death - and it emits navigation/snackbar effects. *About* is deliberately **MVVM**: a `StateFlow` plus a couple of public methods, with **no `Action` and no `Event` types at all**, because that ceremony would be pure overhead for static content. -### Note — Events vs State +### Note - Events vs State State is what the screen **is** (re-rendered on every change, survives recomposition/rotation). -Events are things that happen **once** — navigate, show a snackbar. Modeling a one-time effect as +Events are things that happen **once** - navigate, show a snackbar. Modeling a one-time effect as state causes it to re-fire on rotation; modeling durable data as an event drops it. MVI keeps them separate (`StateFlow` vs `Channel`); the Compose side consumes events with `ObserveAsEvents`, the Views side with `repeatOnLifecycle`. -### Note — when MVVM is acceptable +### Note - when MVVM is acceptable Reach for MVI when state is complex **and** side effects matter. Reach for plain MVVM when the screen -is small, mostly static, and has no real side effects — the *About* screen is the canonical case. +is small, mostly static, and has no real side effects - the *About* screen is the canonical case. --- ## One ViewModel, two renderers (Compose vs Views) -`:feature:characters:presentation` is **UI-toolkit-agnostic** — no Compose *and* no Views dependency. +`:feature:characters:presentation` is **UI-toolkit-agnostic** - no Compose *and* no Views dependency. State stays Compose-stable via `kotlinx-collections-immutable` (`ImmutableList`) rather than the `@Stable` annotation (which would pull in compose-runtime). The exact same `CharacterListViewModel` (State/Action/Event/UI-model) is rendered twice: -- `:feature:characters:presentation-compose` — Jetpack Compose (`LazyColumn`). -- `:feature:characters:presentation-views` — `Fragment` + ViewBinding + `RecyclerView`/`DiffUtil`, +- `:feature:characters:presentation-compose` - Jetpack Compose (`LazyColumn`). +- `:feature:characters:presentation-views` - `Fragment` + ViewBinding + `RecyclerView`/`DiffUtil`, resolving the **same** Koin `CharacterListViewModel` via `by viewModel()`. `:app` hosts the Views renderer inside the Compose `NavHost` via `AndroidFragment` (Compose↔View @@ -196,7 +196,7 @@ sealed interface DataError : Error { enum Network { NO_INTERNET, NOT_FOUND, SERV ``` - The **data layer** catches transport/parse exceptions at the boundary (`safeCall` in `:core:data`) - and converts them to `Result.Error(DataError.Network.*)` — HTTP status → typed error, and a + and converts them to `Result.Error(DataError.Network.*)` - HTTP status → typed error, and a malformed body → `SERIALIZATION` (the cause chain is unwrapped because Ktor wraps the kotlinx `SerializationException`). Upper layers never see raw exceptions. - The **presentation layer** maps a `DataError` to user-facing **`UiText`** via `DataError.toUiText()` @@ -237,10 +237,10 @@ in `:app`. - **Intra-feature** navigation (list → detail, list → error demo) is driven by the `NavController` passed into `charactersGraph(navController, …)`. - **Cross-feature / cross-toolkit** destinations (About, the Views renderer) are exposed as **lambda - callbacks** supplied by `:app` — a feature never imports another feature's route. + callbacks** supplied by `:app` - a feature never imports another feature's route. - **Nav args without a nav dependency:** type-safe nav serializes `CharacterDetailRoute.characterId` into the destination's arguments, which Navigation copies into the ViewModel's `SavedStateHandle`. - `CharacterDetailViewModel` reads `savedStateHandle.get("characterId")` by field name — so the + `CharacterDetailViewModel` reads `savedStateHandle.get("characterId")` by field name - so the UI-agnostic `presentation` module needs **no** navigation dependency. The same `SavedStateHandle` also persists the list's loaded page across process death. @@ -249,7 +249,7 @@ in `:app`. ## Dependency injection (Koin) One Koin module per feature layer (only if it has something to provide), all assembled in -`ArchitectureApp` — never inside feature modules. Prefer the **constructor DSL**: +`ArchitectureApp` - never inside feature modules. Prefer the **constructor DSL**: ```kotlin // :feature:characters:data @@ -268,8 +268,8 @@ val charactersPresentationModule = module { ``` The lambda form (`single { … }`) appears only where a constructor reference can't express the binding -— e.g. `single { HttpClientFactory.create(OkHttp.create()) }` in `coreDataModule` (a factory call, -not a constructor). Compose roots inject with `koinViewModel()`; the Fragment uses `by viewModel()` — +- e.g. `single { HttpClientFactory.create(OkHttp.create()) }` in `coreDataModule` (a factory call, +not a constructor). Compose roots inject with `koinViewModel()`; the Fragment uses `by viewModel()` - both resolve the **same** `CharacterListViewModel` class and supply its `SavedStateHandle`. --- @@ -289,7 +289,7 @@ Tests prove the architecture, not just the code. Stack: **JUnit 5**, **MockK**, Conventions demonstrated: - **MockK for collaborators.** The ViewModel/UseCase tests stub the `CharacterRepository` interface - with MockK — `coEvery` scripts the suspend calls, `coVerify` asserts the paging/retry interactions. + with MockK - `coEvery` scripts the suspend calls, `coVerify` asserts the paging/retry interactions. - **VM tested through its public MVI surface** (State/Action/Event) with a directly-constructed `SavedStateHandle`, so the same tests hold for either renderer. Coverage includes happy path, error → `UiText` + snackbar `Event`, pagination end-reached, **process-death restore**, and the @@ -303,7 +303,7 @@ Conventions demonstrated: > **JUnit 5 on AGP 9:** the `de.mannodermaus.android-junit5` Gradle plugin targets AGP 8.x, so this > repo doesn't use it. `AndroidUnitTest` extends Gradle's `Test`, so the `architecture.android.unit.test` -> convention plugin just calls `useJUnitPlatform()` and adds the `unit-test` bundle — including the +> convention plugin just calls `useJUnitPlatform()` and adds the `unit-test` bundle - including the > `junit-platform-launcher`, which Gradle 9 no longer bundles. > **Espresso + API 34+:** Compose's test rule drives Espresso's `onIdle`, and transitive Espresso diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cb519c4..464dd0e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -35,9 +35,9 @@ dependencies { implementation(libs.androidx.navigation.compose) // Compose↔View interop: hosts a Fragment inside the Compose NavHost. implementation(libs.androidx.fragment.compose) - // Material Components — required for the Material3 XML Activity theme. + // Material Components - required for the Material3 XML Activity theme. implementation(libs.material) - // Logging — the DebugTree is planted here; other modules log via Timber's static API. + // Logging - the DebugTree is planted here; other modules log via Timber's static API. implementation(libs.timber) androidTestImplementation(libs.androidx.compose.ui.test.junit4) diff --git a/app/src/main/kotlin/com/example/architecture/CharactersViewsRoute.kt b/app/src/main/kotlin/com/example/architecture/CharactersViewsRoute.kt index 6397732..a5f3f4c 100644 --- a/app/src/main/kotlin/com/example/architecture/CharactersViewsRoute.kt +++ b/app/src/main/kotlin/com/example/architecture/CharactersViewsRoute.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable /** * Route for the characters list rendered with the classic **Views** toolkit. It lives in `:app` - * because `:app` owns Compose↔View interop — the `:feature:characters:presentation-views` module + * because `:app` owns Compose↔View interop - the `:feature:characters:presentation-views` module * stays navigation-agnostic (it knows nothing about Compose Navigation or this route). */ @Serializable diff --git a/build-logic/convention/src/main/kotlin/com/example/architecture/convention/AndroidUnitTestConventionPlugin.kt b/build-logic/convention/src/main/kotlin/com/example/architecture/convention/AndroidUnitTestConventionPlugin.kt index 47afb90..5abf15f 100644 --- a/build-logic/convention/src/main/kotlin/com/example/architecture/convention/AndroidUnitTestConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/com/example/architecture/convention/AndroidUnitTestConventionPlugin.kt @@ -11,7 +11,7 @@ import org.gradle.kotlin.dsl.withType * shared `unit-test` toolset (JUnit Jupiter, kotlinx-coroutines-test, Turbine, AssertK). * * Deliberately does NOT use the `de.mannodermaus.android-junit5` Gradle plugin: its 1.11.x line - * targets AGP 8.x and we build on AGP 9.0. It isn't needed for *local* unit tests anyway — + * targets AGP 8.x and we build on AGP 9.0. It isn't needed for *local* unit tests anyway - * `com.android.build.gradle.tasks.factory.AndroidUnitTest` extends Gradle's [Test] task, so calling * `useJUnitPlatform()` on it is enough (this mirrors `DomainModuleConventionPlugin`, which does the * same for pure-JVM modules). diff --git a/build-logic/convention/src/main/kotlin/com/example/architecture/convention/ComposeConventionPlugin.kt b/build-logic/convention/src/main/kotlin/com/example/architecture/convention/ComposeConventionPlugin.kt index 79571df..a53e219 100644 --- a/build-logic/convention/src/main/kotlin/com/example/architecture/convention/ComposeConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/com/example/architecture/convention/ComposeConventionPlugin.kt @@ -27,7 +27,7 @@ class ComposeConventionPlugin : Plugin { dependencies { // `implementation` (not api): every Compose consumer applies this convention itself, so - // Compose must NOT leak transitively — that keeps the UI-agnostic presentation module + // Compose must NOT leak transitively - that keeps the UI-agnostic presentation module // (which depends on core:presentation) free of Compose. val bom = platform(libs.findLibrary("androidx-compose-bom").get()) add("implementation", bom) diff --git a/core/data/src/main/kotlin/com/example/architecture/core/data/di/CoreDataModule.kt b/core/data/src/main/kotlin/com/example/architecture/core/data/di/CoreDataModule.kt index 4ab8b4d..9910243 100644 --- a/core/data/src/main/kotlin/com/example/architecture/core/data/di/CoreDataModule.kt +++ b/core/data/src/main/kotlin/com/example/architecture/core/data/di/CoreDataModule.kt @@ -6,7 +6,7 @@ import io.ktor.client.engine.okhttp.OkHttp import org.koin.dsl.module /** - * Core data DI: the single shared [HttpClient]. This is the one sanctioned lambda-DSL binding — + * Core data DI: the single shared [HttpClient]. This is the one sanctioned lambda-DSL binding - * HttpClient is assembled by a factory plus the OkHttp engine (not a plain constructor), so the * constructor DSL (`singleOf`) cannot express it. Feature data modules append their own bindings. */ diff --git a/core/design-system/src/main/kotlin/com/example/architecture/core/design/system/theme/Color.kt b/core/design-system/src/main/kotlin/com/example/architecture/core/design/system/theme/Color.kt index 505b7d1..0e4d794 100644 --- a/core/design-system/src/main/kotlin/com/example/architecture/core/design/system/theme/Color.kt +++ b/core/design-system/src/main/kotlin/com/example/architecture/core/design/system/theme/Color.kt @@ -4,7 +4,7 @@ import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.ui.graphics.Color -// Brand palette — seeded from the Android green used by the project. +// Brand palette - seeded from the Android green used by the project. private val Green10 = Color(0xFF00210B) private val Green20 = Color(0xFF003918) private val Green40 = Color(0xFF1E6C36) diff --git a/core/domain/src/main/kotlin/com/example/architecture/core/domain/Result.kt b/core/domain/src/main/kotlin/com/example/architecture/core/domain/Result.kt index c50b076..ac4e004 100644 --- a/core/domain/src/main/kotlin/com/example/architecture/core/domain/Result.kt +++ b/core/domain/src/main/kotlin/com/example/architecture/core/domain/Result.kt @@ -13,7 +13,7 @@ sealed interface Result { ) : Result } -/** A [Result] that carries no success payload — for operations that either succeed or fail. */ +/** A [Result] that carries no success payload - for operations that either succeed or fail. */ typealias EmptyResult = Result inline fun Result.map(map: (T) -> R): Result { diff --git a/feature/about/presentation/src/main/kotlin/com/example/architecture/feature/about/presentation/AboutNavigation.kt b/feature/about/presentation/src/main/kotlin/com/example/architecture/feature/about/presentation/AboutNavigation.kt index 9462541..1fd6032 100644 --- a/feature/about/presentation/src/main/kotlin/com/example/architecture/feature/about/presentation/AboutNavigation.kt +++ b/feature/about/presentation/src/main/kotlin/com/example/architecture/feature/about/presentation/AboutNavigation.kt @@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable data object AboutRoute /** - * The About feature nav graph. It only needs a "go back" callback — `:app` wires it to the shared + * The About feature nav graph. It only needs a "go back" callback - `:app` wires it to the shared * NavController, keeping this feature decoupled from how it is reached. */ fun NavGraphBuilder.aboutGraph( diff --git a/feature/about/presentation/src/main/kotlin/com/example/architecture/feature/about/presentation/AboutScreen.kt b/feature/about/presentation/src/main/kotlin/com/example/architecture/feature/about/presentation/AboutScreen.kt index daf2caa..838f805 100644 --- a/feature/about/presentation/src/main/kotlin/com/example/architecture/feature/about/presentation/AboutScreen.kt +++ b/feature/about/presentation/src/main/kotlin/com/example/architecture/feature/about/presentation/AboutScreen.kt @@ -30,7 +30,7 @@ import com.example.architecture.feature.about.presentation.model.AboutLink /** * Root for the MVVM About screen. Note how different the wiring is from an MVI Root: it collects - * [AboutState] and passes the ViewModel's **method reference** straight through — there is no + * [AboutState] and passes the ViewModel's **method reference** straight through - there is no * `onAction` funnel and no event observation, because this screen has neither. */ @Composable @@ -88,7 +88,7 @@ fun AboutScreen( Text(text = "• $highlight", style = MaterialTheme.typography.bodyMedium) } - // The expandable card is driven entirely by the VM's plain method — the MVVM contrast. + // The expandable card is driven entirely by the VM's plain method - the MVVM contrast. AppCard( onClick = onToggleMvvmNote, header = { diff --git a/feature/about/presentation/src/main/kotlin/com/example/architecture/feature/about/presentation/AboutViewModel.kt b/feature/about/presentation/src/main/kotlin/com/example/architecture/feature/about/presentation/AboutViewModel.kt index 3543016..c3907aa 100644 --- a/feature/about/presentation/src/main/kotlin/com/example/architecture/feature/about/presentation/AboutViewModel.kt +++ b/feature/about/presentation/src/main/kotlin/com/example/architecture/feature/about/presentation/AboutViewModel.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update /** - * **MVVM — the deliberate contrast to the app's MVI screens.** + * **MVVM - the deliberate contrast to the app's MVI screens.** * * There is no `Action` sealed type and no `Event`/effect `Channel`. The screen reads [state] and * invokes the ViewModel's **plain public methods** directly. That is the whole point of this screen: @@ -37,7 +37,7 @@ class AboutViewModel : ViewModel() { ), mvvmNote = "MVI funnels every user intent through a single onAction(Action) entry point " + "and emits one-time effects (navigation, snackbars) through an Event channel. That " + - "structure pays off when state is complex and interacting — like the paginated, " + + "structure pays off when state is complex and interacting - like the paginated, " + "process-death-restorable characters list. This screen is intentionally MVVM instead: " + "the ViewModel exposes a StateFlow plus plain public methods (onToggleMvvmNote), with " + "no Action or Event types at all. Rule of thumb: reach for MVI when state is complex " + @@ -56,7 +56,7 @@ class AboutViewModel : ViewModel() { ) val state: StateFlow = _state.asStateFlow() - /** MVVM: a plain public method mutates state directly — no Action object, no reducer funnel. */ + /** MVVM: a plain public method mutates state directly - no Action object, no reducer funnel. */ fun onToggleMvvmNote() { _state.update { it.copy(showMvvmNote = !it.showMvvmNote) } } diff --git a/feature/characters/data/src/main/kotlin/com/example/architecture/feature/characters/data/datasource/KtorCharacterDataSource.kt b/feature/characters/data/src/main/kotlin/com/example/architecture/feature/characters/data/datasource/KtorCharacterDataSource.kt index 8b6298b..4a507b7 100644 --- a/feature/characters/data/src/main/kotlin/com/example/architecture/feature/characters/data/datasource/KtorCharacterDataSource.kt +++ b/feature/characters/data/src/main/kotlin/com/example/architecture/feature/characters/data/datasource/KtorCharacterDataSource.kt @@ -8,7 +8,7 @@ import com.example.architecture.feature.characters.data.dto.CharactersResponseDt import io.ktor.client.HttpClient /** - * Remote data source for characters. Returns raw DTOs (no mapping here — the repository maps via + * Remote data source for characters. Returns raw DTOs (no mapping here - the repository maps via * CharacterMapper). Errors already surface as [DataError.Network] from the typed `get` helper. */ internal class KtorCharacterDataSource( diff --git a/feature/characters/data/src/test/kotlin/com/example/architecture/feature/characters/data/NetworkCharacterRepositoryTest.kt b/feature/characters/data/src/test/kotlin/com/example/architecture/feature/characters/data/NetworkCharacterRepositoryTest.kt index 46de1f7..f63d62b 100644 --- a/feature/characters/data/src/test/kotlin/com/example/architecture/feature/characters/data/NetworkCharacterRepositoryTest.kt +++ b/feature/characters/data/src/test/kotlin/com/example/architecture/feature/characters/data/NetworkCharacterRepositoryTest.kt @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test /** * Data-layer test for [NetworkCharacterRepository]. A Ktor [MockEngine] is swapped into the real - * [HttpClientFactory] (`create(engine)` takes the engine precisely so tests can do this) — so the + * [HttpClientFactory] (`create(engine)` takes the engine precisely so tests can do this) - so the * full path under test is genuine: Ktor request → status/JSON handling in `safeCall` → DTO mapping → * domain model. Covers success mapping, a 404 and a 5xx mapped to typed [DataError.Network], and a * malformed-body → SERIALIZATION case. diff --git a/feature/characters/domain/src/main/kotlin/com/example/architecture/feature/characters/domain/usecase/GetCharactersPageUseCase.kt b/feature/characters/domain/src/main/kotlin/com/example/architecture/feature/characters/domain/usecase/GetCharactersPageUseCase.kt index fb87121..39d91e5 100644 --- a/feature/characters/domain/src/main/kotlin/com/example/architecture/feature/characters/domain/usecase/GetCharactersPageUseCase.kt +++ b/feature/characters/domain/src/main/kotlin/com/example/architecture/feature/characters/domain/usecase/GetCharactersPageUseCase.kt @@ -9,14 +9,14 @@ import com.example.architecture.feature.characters.domain.model.CharactersPage * Loads one page of characters. * * **When to add a UseCase (convention note):** introduce a UseCase when a screen needs business - * logic that does NOT belong in the ViewModel — non-trivial rules, or *composition* of several + * logic that does NOT belong in the ViewModel - non-trivial rules, or *composition* of several * repositories/sources into one domain operation. When the ViewModel would merely forward a single * repository call, skipping the UseCase and injecting the repository directly is perfectly fine. * * This particular UseCase is a **thin pass-through, included for illustration**: it adds no logic * beyond delegating to [CharacterRepository]. It earns its place only as a showcase of the * convention (domain-owned, `operator fun invoke`, constructor-injected). In a real app you would - * grow it the moment list loading gained real behaviour (filtering, merging a local cache, …) — or + * grow it the moment list loading gained real behaviour (filtering, merging a local cache, …) - or * delete it and let the ViewModel call the repository. */ class GetCharactersPageUseCase( diff --git a/feature/characters/domain/src/test/kotlin/com/example/architecture/feature/characters/domain/usecase/GetCharactersPageUseCaseTest.kt b/feature/characters/domain/src/test/kotlin/com/example/architecture/feature/characters/domain/usecase/GetCharactersPageUseCaseTest.kt index aee640b..6651b5a 100644 --- a/feature/characters/domain/src/test/kotlin/com/example/architecture/feature/characters/domain/usecase/GetCharactersPageUseCaseTest.kt +++ b/feature/characters/domain/src/test/kotlin/com/example/architecture/feature/characters/domain/usecase/GetCharactersPageUseCaseTest.kt @@ -17,7 +17,7 @@ import org.junit.jupiter.api.Test /** * Tests for the (thin pass-through) [GetCharactersPageUseCase]: it must forward the requested page to - * the repository and return its result verbatim — success and error alike. Pure JVM test on the + * the repository and return its result verbatim - success and error alike. Pure JVM test on the * JUnit 5 platform (see DomainModuleConventionPlugin); the [CharacterRepository] collaborator is a * MockK mock, stubbed with `coEvery` and verified with `coVerify`. */ diff --git a/feature/characters/presentation-compose/src/androidTest/kotlin/com/example/architecture/feature/characters/presentation/compose/CharacterListRobot.kt b/feature/characters/presentation-compose/src/androidTest/kotlin/com/example/architecture/feature/characters/presentation/compose/CharacterListRobot.kt index 35de864..b729892 100644 --- a/feature/characters/presentation-compose/src/androidTest/kotlin/com/example/architecture/feature/characters/presentation/compose/CharacterListRobot.kt +++ b/feature/characters/presentation-compose/src/androidTest/kotlin/com/example/architecture/feature/characters/presentation/compose/CharacterListRobot.kt @@ -13,7 +13,7 @@ import org.junit.Assert.assertTrue /** * Robot for [CharacterListScreen] UI tests. Each method returns `this` so calls read as a fluent * scenario (`robot.setContent(state).assertCharacterShown(...).clickCharacter(...)`). The robot owns - * the interaction vocabulary; the test owns the assertions' intent — keeping tests readable and + * the interaction vocabulary; the test owns the assertions' intent - keeping tests readable and * resilient to UI structure changes. */ class CharacterListRobot( diff --git a/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharacterDetailScreen.kt b/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharacterDetailScreen.kt index db46a47..f2ace07 100644 --- a/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharacterDetailScreen.kt +++ b/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharacterDetailScreen.kt @@ -68,7 +68,7 @@ fun CharacterDetailRoot( CharacterDetailScreen(state = state, onAction = viewModel::onAction) } -/** Pure, stateless screen — previewable without a ViewModel. */ +/** Pure, stateless screen - previewable without a ViewModel. */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun CharacterDetailScreen( diff --git a/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharacterListScreen.kt b/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharacterListScreen.kt index 594519e..1426a29 100644 --- a/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharacterListScreen.kt +++ b/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharacterListScreen.kt @@ -101,7 +101,7 @@ fun CharacterListRoot( ) } -/** Pure, stateless screen — previewable without a ViewModel. */ +/** Pure, stateless screen - previewable without a ViewModel. */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun CharacterListScreen( diff --git a/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharactersNavigation.kt b/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharactersNavigation.kt index 8098697..3d9ef27 100644 --- a/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharactersNavigation.kt +++ b/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharactersNavigation.kt @@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable @Serializable data object CharacterListRoute -/** Type-safe route for the character detail screen — carries only the typed id, never an object. */ +/** Type-safe route for the character detail screen - carries only the typed id, never an object. */ @Serializable data class CharacterDetailRoute(val characterId: Int) @@ -39,7 +39,7 @@ fun NavGraphBuilder.charactersGraph( } composable { // The typed CharacterDetailRoute serializes `characterId` into the destination's arguments, - // which Navigation copies into the ViewModel's SavedStateHandle — that is where + // which Navigation copies into the ViewModel's SavedStateHandle - that is where // CharacterDetailViewModel reads it (keeping that module free of any navigation dependency). CharacterDetailRoot(onNavigateBack = { navController.popBackStack() }) } diff --git a/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/ErrorDemoScreen.kt b/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/ErrorDemoScreen.kt index cf20c44..a513a47 100644 --- a/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/ErrorDemoScreen.kt +++ b/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/ErrorDemoScreen.kt @@ -57,7 +57,7 @@ fun ErrorDemoRoot( ErrorDemoScreen(state = state, onAction = viewModel::onAction) } -/** Pure, stateless screen — previewable without a ViewModel. */ +/** Pure, stateless screen - previewable without a ViewModel. */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun ErrorDemoScreen( diff --git a/feature/characters/presentation-views/src/main/kotlin/com/example/architecture/feature/characters/presentation/views/CharacterListFragment.kt b/feature/characters/presentation-views/src/main/kotlin/com/example/architecture/feature/characters/presentation/views/CharacterListFragment.kt index a7543a0..b2c427a 100644 --- a/feature/characters/presentation-views/src/main/kotlin/com/example/architecture/feature/characters/presentation/views/CharacterListFragment.kt +++ b/feature/characters/presentation-views/src/main/kotlin/com/example/architecture/feature/characters/presentation/views/CharacterListFragment.kt @@ -23,7 +23,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel /** * Classic Views renderer for the characters list. It drives the **same** [CharacterListViewModel] as - * the Compose screen — proving the presentation logic (State/Action/Event/UI-model) is truly + * the Compose screen - proving the presentation logic (State/Action/Event/UI-model) is truly * UI-agnostic. Koin's `by viewModel()` supplies the VM (and its `SavedStateHandle`). * * `:app` (the interop owner) wires [onCharacterClick] / [onNavigateBack]; the Fragment never touches diff --git a/feature/characters/presentation-views/src/main/kotlin/com/example/architecture/feature/characters/presentation/views/CharacterStatusViews.kt b/feature/characters/presentation-views/src/main/kotlin/com/example/architecture/feature/characters/presentation/views/CharacterStatusViews.kt index 23d7f80..94b2569 100644 --- a/feature/characters/presentation-views/src/main/kotlin/com/example/architecture/feature/characters/presentation/views/CharacterStatusViews.kt +++ b/feature/characters/presentation-views/src/main/kotlin/com/example/architecture/feature/characters/presentation/views/CharacterStatusViews.kt @@ -6,7 +6,7 @@ import com.example.architecture.feature.characters.domain.model.CharacterStatus /** * Views-renderer presentation helpers for [CharacterStatus]. These intentionally mirror the Compose - * renderer's helpers but return platform types (a string-res id and an ARGB Int) — each renderer + * renderer's helpers but return platform types (a string-res id and an ARGB Int) - each renderer * owns its own resources, so the small label duplication across modules is expected. */ @StringRes diff --git a/feature/characters/presentation/build.gradle.kts b/feature/characters/presentation/build.gradle.kts index b443042..8ceb020 100644 --- a/feature/characters/presentation/build.gradle.kts +++ b/feature/characters/presentation/build.gradle.kts @@ -18,7 +18,7 @@ dependencies { implementation(libs.androidx.lifecycle.viewmodel.ktx) implementation(libs.androidx.lifecycle.viewmodel.savedstate) implementation(libs.kotlinx.coroutines.android) - // Stable collection for state — makes the list Compose-stable WITHOUT a Compose dependency, + // Stable collection for state - makes the list Compose-stable WITHOUT a Compose dependency, // so this module stays UI-agnostic (no @Stable annotation, which would require compose-runtime). // `api` because CharacterListState.characters exposes ImmutableList in the public state API. api(libs.kotlinx.collections.immutable) diff --git a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/CharacterDetailViewModel.kt b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/CharacterDetailViewModel.kt index 4ca224f..696cbdc 100644 --- a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/CharacterDetailViewModel.kt +++ b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/CharacterDetailViewModel.kt @@ -19,7 +19,7 @@ import kotlinx.coroutines.launch * UI-agnostic MVI ViewModel for the character detail screen. * * Type-safe navigation writes the route's typed `characterId` into [SavedStateHandle] under its - * field name. Reading that raw key — instead of `savedStateHandle.toRoute()` — + * field name. Reading that raw key - instead of `savedStateHandle.toRoute()` - * is deliberate: it keeps this module free of any navigation/Compose dependency (the route type * lives in the renderer). The renderer is what reads the route via `toRoute()`. */ diff --git a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/CharacterListState.kt b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/CharacterListState.kt index a3c54c8..08323db 100644 --- a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/CharacterListState.kt +++ b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/CharacterListState.kt @@ -8,7 +8,7 @@ import kotlinx.collections.immutable.persistentListOf /** * The single source of UI state for the characters list. Deliberately Compose-free: instead of the * `@Stable` annotation (which lives in compose-runtime), the list is an [ImmutableList], which - * Compose already treats as stable — so this module needs no Compose dependency. Navigation and + * Compose already treats as stable - so this module needs no Compose dependency. Navigation and * snackbars are one-time Events, never state. */ data class CharacterListState( diff --git a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/CharacterListViewModel.kt b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/CharacterListViewModel.kt index e70c400..7eaa276 100644 --- a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/CharacterListViewModel.kt +++ b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/CharacterListViewModel.kt @@ -77,7 +77,7 @@ class CharacterListViewModel( page++ } - // Always surface a failure — even a partial one where earlier pages loaded. + // Always surface a failure - even a partial one where earlier pages loaded. if (error != null) { _events.send(CharacterListEvent.ShowSnackbar(error)) } @@ -119,7 +119,7 @@ class CharacterListViewModel( private fun loadPage(page: Int) { // Flip the loading flag SYNCHRONOUSLY (before launching) so a rapid second OnLoadNextPage is - // guarded out before its coroutine starts — otherwise the same page loads twice and items + // guarded out before its coroutine starts - otherwise the same page loads twice and items // get appended twice. _state.update { it.copy(isLoadingNextPage = true, error = null) } viewModelScope.launch { diff --git a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/ErrorDemoAction.kt b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/ErrorDemoAction.kt index ad5a90c..3ab8792 100644 --- a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/ErrorDemoAction.kt +++ b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/ErrorDemoAction.kt @@ -4,7 +4,7 @@ sealed interface ErrorDemoAction { /** Force a load that fails with the given [ErrorScenario]. */ data class OnForceError(val scenario: ErrorScenario) : ErrorDemoAction - /** Force a load that succeeds — clears any current error. */ + /** Force a load that succeeds - clears any current error. */ data object OnLoadSuccess : ErrorDemoAction /** Re-issue the most recent load (the design-system retry button). */ diff --git a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/ErrorDemoState.kt b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/ErrorDemoState.kt index d9069bc..29820cf 100644 --- a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/ErrorDemoState.kt +++ b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/ErrorDemoState.kt @@ -4,8 +4,8 @@ import com.example.architecture.core.presentation.UiText /** * State for the error-handling demo. All fields are primitive/stable, so no `@Stable` is needed. - * [error] is the *mapped* [UiText] produced by `DataError.toUiText()` — exactly what the real - * screens hold — so the renderer resolves and shows it the same way. + * [error] is the *mapped* [UiText] produced by `DataError.toUiText()` - exactly what the real + * screens hold - so the renderer resolves and shows it the same way. */ data class ErrorDemoState( val isLoading: Boolean = false, diff --git a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/ErrorDemoViewModel.kt b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/ErrorDemoViewModel.kt index 7ca1a7f..9990906 100644 --- a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/ErrorDemoViewModel.kt +++ b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/ErrorDemoViewModel.kt @@ -16,7 +16,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch /** - * UI-agnostic MVI ViewModel for the **error-handling demo** — a runnable walk-through of the whole + * UI-agnostic MVI ViewModel for the **error-handling demo** - a runnable walk-through of the whole * error pipeline. A "force error" affordance produces a real [DataError.Network], which is routed * through the *same* steps a genuine network call uses: * @@ -24,8 +24,8 @@ import kotlinx.coroutines.launch * Result<…, DataError.Network> → onSuccess / onFailure → DataError.toUiText() → ErrorState * ``` * - * The outcome is *simulated* (no real request) only so every case — including NO_INTERNET, which you - * can't reliably trigger on demand — is reachable deterministically. [OnRetry] re-issues the last + * The outcome is *simulated* (no real request) only so every case - including NO_INTERNET, which you + * can't reliably trigger on demand - is reachable deterministically. [OnRetry] re-issues the last * attempt (proving retry is an Action); [OnLoadSuccess] clears the error (proving it clears on * success). */ diff --git a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/di/CharactersPresentationModule.kt b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/di/CharactersPresentationModule.kt index b03ddc8..1c32500 100644 --- a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/di/CharactersPresentationModule.kt +++ b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/di/CharactersPresentationModule.kt @@ -10,7 +10,7 @@ import org.koin.dsl.module /** Presentation DI for the characters feature. Lives with the (UI-agnostic) ViewModels it provides. */ val charactersPresentationModule = module { - // Stateless domain UseCase — `factoryOf` (a fresh, cheap instance per resolution). Koin supplies + // Stateless domain UseCase - `factoryOf` (a fresh, cheap instance per resolution). Koin supplies // its CharacterRepository from charactersDataModule. factoryOf(::GetCharactersPageUseCase) viewModelOf(::CharacterListViewModel) diff --git a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/model/CharacterUi.kt b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/model/CharacterUi.kt index ad32d46..bbef40f 100644 --- a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/model/CharacterUi.kt +++ b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/model/CharacterUi.kt @@ -3,7 +3,7 @@ package com.example.architecture.feature.characters.presentation.model import com.example.architecture.feature.characters.domain.model.Character import com.example.architecture.feature.characters.domain.model.CharacterStatus -/** Presentation model for a character list item — decouples the UI from the domain [Character]. */ +/** Presentation model for a character list item - decouples the UI from the domain [Character]. */ data class CharacterUi( val id: Int, val name: String, diff --git a/feature/characters/presentation/src/test/kotlin/com/example/architecture/feature/characters/presentation/CharacterDetailViewModelTest.kt b/feature/characters/presentation/src/test/kotlin/com/example/architecture/feature/characters/presentation/CharacterDetailViewModelTest.kt index 7731c9d..da616e4 100644 --- a/feature/characters/presentation/src/test/kotlin/com/example/architecture/feature/characters/presentation/CharacterDetailViewModelTest.kt +++ b/feature/characters/presentation/src/test/kotlin/com/example/architecture/feature/characters/presentation/CharacterDetailViewModelTest.kt @@ -27,7 +27,7 @@ import org.junit.jupiter.api.assertThrows /** * Unit tests for [CharacterDetailViewModel]. The character id arrives via [SavedStateHandle] (written - * by type-safe navigation), which is constructed directly here — proving the VM needs no navigation + * by type-safe navigation), which is constructed directly here - proving the VM needs no navigation * dependency. The [CharacterRepository] collaborator is a *relaxed* MockK mock, so the "missing id" * case needs no stubbing while the rest stub `getCharacterDetails` explicitly with `coEvery`; * assertions use AssertK; the back event is observed with Turbine. @@ -85,7 +85,7 @@ class CharacterDetailViewModelTest { advanceUntilIdle() assertThat(viewModel.state.value.error).isNotNull() - // Same call, new answer — the latest `coEvery` wins, so the retry attempt succeeds. + // Same call, new answer - the latest `coEvery` wins, so the retry attempt succeeds. coEvery { repository.getCharacterDetails(1) } returns Result.Success(characterDetails(1)) viewModel.onAction(CharacterDetailAction.OnRetry) advanceUntilIdle() diff --git a/feature/characters/presentation/src/test/kotlin/com/example/architecture/feature/characters/presentation/CharacterListViewModelTest.kt b/feature/characters/presentation/src/test/kotlin/com/example/architecture/feature/characters/presentation/CharacterListViewModelTest.kt index 9ff0b02..1429367 100644 --- a/feature/characters/presentation/src/test/kotlin/com/example/architecture/feature/characters/presentation/CharacterListViewModelTest.kt +++ b/feature/characters/presentation/src/test/kotlin/com/example/architecture/feature/characters/presentation/CharacterListViewModelTest.kt @@ -33,7 +33,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test /** - * Unit tests for [CharacterListViewModel] — driven entirely through its public MVI surface + * Unit tests for [CharacterListViewModel] - driven entirely through its public MVI surface * (State/Action/Event), so they prove the VM correct regardless of which renderer hosts it. * * Uses [StandardTestDispatcher] (not Unconfined) so launched work is queued until `advanceUntilIdle`, @@ -159,7 +159,7 @@ class CharacterListViewModelTest { viewModel.onAction(CharacterListAction.OnLoadNextPage) advanceUntilIdle() - // Only the single initial load ran — the guarded next-page request never fired. + // Only the single initial load ran - the guarded next-page request never fired. coVerify(exactly = 1) { repository.getCharacters(1) } coVerify(exactly = 0) { repository.getCharacters(2) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 43e5901..7374fad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ agp = "9.0.1" kotlin = "2.3.20" -# AndroidX – core / lifecycle / activity / views +# AndroidX - core / lifecycle / activity / views androidxCore = "1.18.0" androidxLifecycle = "2.10.0" androidxActivity = "1.13.0" @@ -116,7 +116,7 @@ timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } # --- Testing --- junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junitJupiter" } junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junitJupiter" } -# Gradle 9 no longer bundles the launcher — it must be on the test runtime classpath explicitly. +# Gradle 9 no longer bundles the launcher - it must be on the test runtime classpath explicitly. junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junitPlatform" } turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } assertk = { module = "com.willowtreeapps.assertk:assertk", version.ref = "assertk" }