-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
334 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import com.vanniktech.maven.publish.SonatypeHost | ||
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.dokka) | ||
id("maven-publish") | ||
id("com.vanniktech.maven.publish") version "0.29.0" | ||
} | ||
|
||
kotlin { | ||
@OptIn(ExperimentalWasmDsl::class) | ||
wasmJs { | ||
moduleName = "composeApp" | ||
browser { | ||
val projectDirPath = project.projectDir.path | ||
commonWebpackConfig { | ||
outputFileName = "composeApp.js" | ||
devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { | ||
static = (static ?: mutableListOf()).apply { | ||
// Serve sources to debug inside browser | ||
add(projectDirPath) | ||
} | ||
} | ||
} | ||
} | ||
binaries.executable() | ||
} | ||
|
||
androidTarget { | ||
@OptIn(ExperimentalKotlinGradlePluginApi::class) | ||
compilerOptions { | ||
jvmTarget.set(JvmTarget.JVM_11) | ||
} | ||
publishLibraryVariants("release", "debug") | ||
} | ||
|
||
jvm("desktop") | ||
|
||
listOf( | ||
iosX64(), | ||
iosArm64(), | ||
iosSimulatorArm64() | ||
).forEach { iosTarget -> | ||
iosTarget.binaries.framework { | ||
baseName = "network-actions" | ||
isStatic = true | ||
} | ||
} | ||
|
||
sourceSets { | ||
val desktopMain by getting | ||
|
||
androidMain.dependencies { | ||
implementation(libs.androidx.activity.compose) | ||
} | ||
commonMain.dependencies { | ||
implementation(libs.androidx.lifecycle.viewmodel) | ||
implementation(projects.core) | ||
implementation(projects.actions) | ||
} | ||
commonTest.dependencies { | ||
implementation(libs.kotlin.test) | ||
implementation(libs.kotlinx.coroutines.test) | ||
} | ||
desktopMain.dependencies { | ||
implementation(libs.kotlinx.coroutines.swing) | ||
} | ||
} | ||
} | ||
|
||
android { | ||
namespace = "com.helloanwar.mvvmate.actions" | ||
compileSdk = libs.versions.android.compileSdk.get().toInt() | ||
|
||
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") | ||
sourceSets["main"].res.srcDirs("src/androidMain/res") | ||
sourceSets["main"].resources.srcDirs("src/commonMain/resources") | ||
|
||
defaultConfig { | ||
minSdk = libs.versions.android.minSdk.get().toInt() | ||
targetSdk = libs.versions.android.targetSdk.get().toInt() | ||
} | ||
packaging { | ||
resources { | ||
excludes += "/META-INF/{AL2.0,LGPL2.1}" | ||
} | ||
} | ||
buildTypes { | ||
getByName("release") { | ||
isMinifyEnabled = false | ||
} | ||
} | ||
compileOptions { | ||
sourceCompatibility = JavaVersion.VERSION_11 | ||
targetCompatibility = JavaVersion.VERSION_11 | ||
} | ||
buildFeatures { | ||
compose = true | ||
} | ||
dependencies { | ||
|
||
} | ||
} | ||
|
||
mavenPublishing { | ||
// Define coordinates for the published artifact | ||
coordinates( | ||
groupId = "com.helloanwar.mvvmate", | ||
artifactId = "network-actions", | ||
version = "0.0.2" | ||
) | ||
|
||
// Configure POM metadata for the published artifact | ||
pom { | ||
name.set("CMP Library for MVVM state management") | ||
description.set("This library is a companion library for MVVM state management") | ||
inceptionYear.set("2024") | ||
url.set("https://github.com/anwarpro/mvvmate") | ||
|
||
licenses { | ||
license { | ||
name.set("MIT") | ||
url.set("https://opensource.org/licenses/MIT") | ||
} | ||
} | ||
|
||
// Specify developer information | ||
developers { | ||
developer { | ||
id.set("anwarpro") | ||
name.set("Mohammad Anwar") | ||
email.set("[email protected]") | ||
} | ||
} | ||
|
||
// Specify SCM information | ||
scm { | ||
url.set("https://github.com/anwarpro/mvvmate") | ||
} | ||
} | ||
|
||
// Configure publishing to Maven Central | ||
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) | ||
|
||
// 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") | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||
|
||
</manifest> |
149 changes: 149 additions & 0 deletions
149
...c/commonMain/kotlin/com/helloanwar/mvvmate/network_actions/BaseNetworkActionsViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package com.helloanwar.mvvmate.network_actions | ||
|
||
import androidx.lifecycle.viewModelScope | ||
import com.helloanwar.mvvmate.actions.BaseActionsViewModel | ||
import com.helloanwar.mvvmate.core.UiAction | ||
import com.helloanwar.mvvmate.core.UiState | ||
import kotlinx.coroutines.Job | ||
import kotlinx.coroutines.TimeoutCancellationException | ||
import kotlinx.coroutines.delay | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.withTimeout | ||
|
||
abstract class BaseNetworkActionsViewModel<S : UiState, A : UiAction>( | ||
initialState: S | ||
) : BaseActionsViewModel<S, A>(initialState) { | ||
|
||
private val ongoingJobs = mutableMapOf<String, Job>() // For tracking cancellable jobs | ||
|
||
// Perform network call with basic support (for global/partial loading) | ||
protected suspend fun <T> performNetworkCall( | ||
isGlobal: Boolean = false, | ||
partialKey: String? = null, | ||
onSuccess: (T) -> Unit, | ||
onError: (String) -> Unit, | ||
networkCall: suspend () -> T | ||
) { | ||
try { | ||
setLoadingState(isGlobal, partialKey) | ||
val result = networkCall() | ||
resetLoadingState(isGlobal, partialKey) | ||
onSuccess(result) | ||
} catch (e: Exception) { | ||
resetLoadingState(isGlobal, partialKey) | ||
onError(e.message ?: "Unknown Error") | ||
} | ||
} | ||
|
||
// Perform network call with retry | ||
protected suspend fun <T> performNetworkCallWithRetry( | ||
retries: Int = 3, | ||
initialDelay: Long = 1000L, | ||
maxDelay: Long = 4000L, | ||
isGlobal: Boolean = false, | ||
partialKey: String? = null, | ||
onSuccess: (T) -> Unit, | ||
onError: (String) -> Unit, | ||
networkCall: suspend () -> T | ||
) { | ||
var currentDelay = initialDelay | ||
repeat(retries) { attempt -> | ||
try { | ||
setLoadingState(isGlobal, partialKey) | ||
val result = networkCall() | ||
resetLoadingState(isGlobal, partialKey) | ||
onSuccess(result) | ||
return | ||
} catch (e: Exception) { | ||
if (attempt == retries - 1) { | ||
resetLoadingState(isGlobal, partialKey) | ||
onError(e.message ?: "Unknown Error") | ||
} else { | ||
delay(currentDelay) | ||
currentDelay = (currentDelay * 2).coerceAtMost(maxDelay) | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Perform network call with timeout | ||
protected suspend fun <T> performNetworkCallWithTimeout( | ||
timeoutMillis: Long = 5000L, | ||
isGlobal: Boolean = false, | ||
partialKey: String? = null, | ||
onSuccess: (T) -> Unit, | ||
onError: (String) -> Unit, | ||
networkCall: suspend () -> T | ||
) { | ||
try { | ||
setLoadingState(isGlobal, partialKey) | ||
val result = withTimeout(timeoutMillis) { networkCall() } | ||
resetLoadingState(isGlobal, partialKey) | ||
onSuccess(result) | ||
} catch (e: TimeoutCancellationException) { | ||
resetLoadingState(isGlobal, partialKey) | ||
onError("Operation timed out") | ||
} catch (e: Exception) { | ||
resetLoadingState(isGlobal, partialKey) | ||
onError(e.message ?: "Unknown Error") | ||
} | ||
} | ||
|
||
// Perform network call with cancellation support | ||
protected fun <T> performNetworkCallWithCancellation( | ||
tag: String, | ||
isGlobal: Boolean = false, | ||
partialKey: String? = null, | ||
onSuccess: (T) -> Unit, | ||
onError: (String) -> Unit, | ||
networkCall: suspend () -> T | ||
) { | ||
// Cancel any ongoing job with the same tag | ||
ongoingJobs[tag]?.cancel() | ||
|
||
val job = viewModelScope.launch { | ||
try { | ||
setLoadingState(isGlobal, partialKey) | ||
val result = networkCall() | ||
resetLoadingState(isGlobal, partialKey) | ||
onSuccess(result) | ||
} catch (e: Exception) { | ||
resetLoadingState(isGlobal, partialKey) | ||
onError(e.message ?: "Unknown Error") | ||
} finally { | ||
ongoingJobs.remove(tag) | ||
} | ||
} | ||
|
||
ongoingJobs[tag] = job | ||
} | ||
|
||
// Helper method to cancel a specific network call | ||
fun cancelNetworkCall(tag: String) { | ||
ongoingJobs[tag]?.cancel() | ||
ongoingJobs.remove(tag) | ||
} | ||
|
||
// Placeholder methods for setting/resetting loading state | ||
private fun setLoadingState(isGlobal: Boolean, partialKey: String?) { | ||
if (isGlobal) { | ||
updateState { setGlobalLoadingState() } | ||
} else if (partialKey != null) { | ||
updateState { setPartialLoadingState(partialKey) } | ||
} | ||
} | ||
|
||
private fun resetLoadingState(isGlobal: Boolean, partialKey: String?) { | ||
if (isGlobal) { | ||
updateState { resetGlobalLoadingState() } | ||
} else if (partialKey != null) { | ||
updateState { resetPartialLoadingState(partialKey) } | ||
} | ||
} | ||
|
||
// Default implementations for loading state can be overridden by subclasses | ||
protected open fun S.setGlobalLoadingState(): S = this | ||
protected open fun S.resetGlobalLoadingState(): S = this | ||
protected open fun S.setPartialLoadingState(key: String): S = this | ||
protected open fun S.resetPartialLoadingState(key: String): S = this | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters