diff --git a/README.md b/README.md index cc1fff9..1e7c84b 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,82 @@ -This is a Kotlin Multiplatform project targeting Android, iOS, Web, Desktop. +# MVVMate -* `/composeApp` is for code that will be shared across your Compose Multiplatform applications. - It contains several subfolders: - - `commonMain` is for code that’s common for all targets. - - Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name. - For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app, - `iosMain` would be the right folder for such calls. +MVVMate is a minimal state management library for Compose Multiplatform, based on the MVVM architecture. -* `/iosApp` contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform, - you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project. +[![Maven Central](https://img.shields.io/maven-central/v/com.helloanwar.mvvmate.core)](https://search.maven.org/artifact/com.helloanwar/mvvmate.core) +[![Documentation](https://img.shields.io/badge/docs-API-blue)](https://anwarpro.github.io/mvvmate) +## Overview -Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html), -[Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/#compose-multiplatform), -[Kotlin/Wasm](https://kotl.in/wasm/)… +This library provides base classes and interfaces for managing UI state, handling user actions, and emitting side effects in a Compose Multiplatform project. -We would appreciate your feedback on Compose/Web and Kotlin/Wasm in the public Slack channel [#compose-web](https://slack-chats.kotlinlang.org/c/compose-web). -If you face any issues, please report them on [GitHub](https://github.com/JetBrains/compose-multiplatform/issues). +### Key Components -You can open the web application by running the `:composeApp:wasmJsBrowserDevelopmentRun` Gradle task. \ No newline at end of file +- **BaseViewModel**: A base ViewModel class that manages state and handles user actions. +- **BaseViewModelWithEffect**: Extends `BaseViewModel` to also manage UI side effects. +- **UiState**: A marker interface for defining UI states. +- **UiAction**: A marker interface for user actions. +- **UiEffect**: A marker interface for UI side effects. + +## Installation + +To use MVVMate in your project, add the following to your `build.gradle.kts`: + +### For Kotlin Multiplatform Projects: + +```kotlin +kotlin { + sourceSets { + val commonMain by getting { + dependencies { + implementation("com.helloanwar.mvvmate.core:1.0.0") + } + } + } +} +``` + +### For Android Projects: + +```kotlin +dependencies { + implementation("com.helloanwar.mvvmate.core:1.0.0") +} +``` + +## Usage + +Here's a simple example of how you can use `MVVMate` in your Compose Multiplatform project: + +```kotlin +// Define your UI State +data class MyUiState(val message: String = "") : UiState + +// Define your User Actions +sealed class MyUiAction : UiAction { + object ShowMessage : MyUiAction() +} + +// Define your ViewModel +class MyViewModel : BaseViewModel(MyUiState()) { + override suspend fun onAction(action: MyUiAction) { + when (action) { + MyUiAction.ShowMessage -> updateState { copy(message = "Hello, World!") } + } + } +} + +// Use the ViewModel in your Composable function +@Composable +fun MyScreen(viewModel: MyViewModel) { + val state by viewModel.state.collectAsState() + + Text(text = state.message) + Button(onClick = { viewModel.handleAction(MyUiAction.ShowMessage) }) { + Text("Show Message") + } +} +``` + +## API Documentation + +For more detailed information, you can check the [API documentation](https://anwarpro.github.io/mvvmate) generated by Dokka. diff --git a/build.gradle.kts b/build.gradle.kts index d4dfc1b..b3c6f0d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,5 +6,6 @@ plugins { alias(libs.plugins.jetbrainsCompose) apply false alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.kotlinMultiplatform) apply false - alias(libs.plugins.jreleaser) apply false +// alias(libs.plugins.jreleaser) apply false + alias(libs.plugins.dokka) apply false } \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index e9fc5a2..75545bb 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -3,12 +3,14 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig +import java.net.URL plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidLibrary) alias(libs.plugins.jetbrainsCompose) alias(libs.plugins.compose.compiler) + alias(libs.plugins.dokka) id("maven-publish") id("com.vanniktech.maven.publish") version "0.29.0" } @@ -160,4 +162,28 @@ mavenPublishing { // Enable GPG signing for all publications signAllPublications() +} + +tasks.dokkaHtml { + outputDirectory.set(buildDir.resolve("dokka")) + dokkaSourceSets { + configureEach { + // Links to external documentation (e.g., coroutines, Android APIs, etc.) + externalDocumentationLink { + url.set(URL("https://kotlinlang.org/api/latest/jvm/stdlib/")) + } + } + } +} + +tasks.dokkaHtml.configure { + dokkaSourceSets { + named("commonMain") { + sourceLink { + localDirectory.set(file("src/commonMain/kotlin")) + remoteUrl.set(URL("https://github.com/anwarpro/mvvmate/blob/main/src/commonMain/kotlin")) + remoteLineSuffix.set("#L") + } + } + } } \ No newline at end of file diff --git a/core/src/commonMain/composeResources/drawable/compose-multiplatform.xml b/core/src/commonMain/composeResources/drawable/compose-multiplatform.xml deleted file mode 100644 index c0bcfb2..0000000 --- a/core/src/commonMain/composeResources/drawable/compose-multiplatform.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/BaseViewModel.kt b/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/BaseViewModel.kt index 1082e51..53b5c90 100644 --- a/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/BaseViewModel.kt +++ b/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/BaseViewModel.kt @@ -8,25 +8,50 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.Dispatchers +/** + * Base class for ViewModels that manages UI state and handles user actions. + * + * This class holds the common logic for state management using [StateFlow] and + * provides a mechanism to handle actions asynchronously using Kotlin coroutines. + * + * @param S The type of UI state that this ViewModel manages. It should implement [UiState]. + * @param A The type of user actions (intents) that this ViewModel responds to. It should implement [UiAction]. + * @property initialState The initial state of the ViewModel when it is created. + */ abstract class BaseViewModel( initialState: S ) : ViewModel() { + /** + * StateFlow that represents the current UI state. + */ private val _state = MutableStateFlow(initialState) val state: StateFlow get() = _state - // Expose a method to update the state + /** + * Method to update the current state of the UI. + * + * @param reducer A lambda that defines how to modify the state. + */ protected fun updateState(reducer: S.() -> S) { _state.update { it.reducer() } } - // Handle intents or actions + /** + * Handles a user action and updates the state accordingly using the provided [onAction] method. + * + * @param action The user action to handle. + */ fun handleAction(action: A) { CoroutineScope(Dispatchers.Main).launch { onAction(action) } } - // Abstract method to handle actions + /** + * Abstract method to be implemented by subclasses to handle the specific action logic. + * + * @param action The user action to be handled. + */ abstract suspend fun onAction(action: A) } diff --git a/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/BaseViewModelWithEffect.kt b/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/BaseViewModelWithEffect.kt index 077b536..5d88647 100644 --- a/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/BaseViewModelWithEffect.kt +++ b/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/BaseViewModelWithEffect.kt @@ -3,13 +3,33 @@ package com.helloanwar.mvvmate.core import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow +/** + * Base class for ViewModels that manages UI state, handles user actions, and emits side effects. + * + * This class extends [BaseViewModel] by adding support for emitting side effects + * via a [SharedFlow]. Side effects are typically used for actions that should not + * directly mutate the UI state but trigger external events, such as navigation or showing a dialog. + * + * @param S The type of UI state that this ViewModel manages. It should implement [UiState]. + * @param A The type of user actions (intents) that this ViewModel responds to. It should implement [UiAction]. + * @param E The type of side effects that this ViewModel emits. + * @property initialState The initial state of the ViewModel when it is created. + */ abstract class BaseViewModelWithEffect( initialState: S ) : BaseViewModel(initialState) { + /** + * A [SharedFlow] for emitting one-time side effects such as navigation or showing dialogs. + */ private val _sideEffects = MutableSharedFlow() val sideEffects: SharedFlow get() = _sideEffects + /** + * Emit a side effect to be handled by the UI. + * + * @param effect The side effect to emit. + */ protected suspend fun emitSideEffect(effect: E) { _sideEffects.emit(effect) } diff --git a/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/Platform.kt b/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/Platform.kt index f727e13..0158033 100644 --- a/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/Platform.kt +++ b/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/Platform.kt @@ -1,7 +1,24 @@ package com.helloanwar.mvvmate.core +/** + * Platform interface representing platform-specific implementations. + * + * This interface is expected to be implemented by each target in a Kotlin Multiplatform project (e.g., Android, iOS). + * It is typically used to get platform-specific information like the name of the platform. + */ interface Platform { + /** + * The name of the platform (e.g., "Android", "iOS"). + */ val name: String } + +/** + * Function to get the platform-specific implementation of [Platform]. + * + * This function is expected to be implemented separately in each platform's source set. + * + * @return A [Platform] instance that represents the current platform. + */ expect fun getPlatform(): Platform \ No newline at end of file diff --git a/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/UiAction.kt b/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/UiAction.kt index a7e5d48..b359fa0 100644 --- a/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/UiAction.kt +++ b/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/UiAction.kt @@ -1,3 +1,9 @@ package com.helloanwar.mvvmate.core +/** + * Marker interface representing an action or intent from the user. + * + * This interface is used in the MVVM pattern to define user actions that are handled by the ViewModel. + * Examples of actions might include button clicks, text input changes, etc. + */ interface UiAction \ No newline at end of file diff --git a/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/UiEffect.kt b/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/UiEffect.kt index ba20170..78d1f85 100644 --- a/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/UiEffect.kt +++ b/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/UiEffect.kt @@ -1,3 +1,9 @@ package com.helloanwar.mvvmate.core +/** + * Marker interface representing a UI side effect. + * + * Side effects are typically actions that do not directly modify the UI state but are meant to trigger external events, + * such as showing a toast, navigating to another screen, or showing a dialog. + */ interface UiEffect \ No newline at end of file diff --git a/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/UiState.kt b/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/UiState.kt index 13a7234..83e006d 100644 --- a/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/UiState.kt +++ b/core/src/commonMain/kotlin/com/helloanwar/mvvmate/core/UiState.kt @@ -1,3 +1,9 @@ package com.helloanwar.mvvmate.core +/** + * Marker interface representing the state of the UI. + * + * This interface is implemented by classes that define the data necessary to render the UI. + * In the MVVM pattern, the state represents the current state of the ViewModel and is observed by the UI. + */ interface UiState \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8b20104..b5b57d1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ junit = "4.13.2" kotlin = "2.0.20" kotlinx-coroutines = "1.8.1" jreleaser = "1.7.0" +dokka = "1.9.0" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -40,4 +41,5 @@ androidLibrary = { id = "com.android.library", version.ref = "agp" } jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } -jreleaser = { id = "org.jreleaser", version.ref = "jreleaser" } \ No newline at end of file +jreleaser = { id = "org.jreleaser", version.ref = "jreleaser" } +dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } \ No newline at end of file