Skip to content

Commit

Permalink
init: network actions
Browse files Browse the repository at this point in the history
  • Loading branch information
anwarpro committed Oct 15, 2024
1 parent 1076dbf commit 812c041
Show file tree
Hide file tree
Showing 4 changed files with 334 additions and 1 deletion.
177 changes: 177 additions & 0 deletions network-actions/build.gradle.kts
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")
}
}
}
}
4 changes: 4 additions & 0 deletions network-actions/src/androidMain/AndroidManifest.xml
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>
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
}
5 changes: 4 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ dependencyResolutionManagement {
}

include(":composeApp")
include(":core")
include(":core")
include(":network")
include(":actions")
include(":network-actions")

0 comments on commit 812c041

Please sign in to comment.