Skip to content

Commit

Permalink
icerockdev#183 state remastering v2
Browse files Browse the repository at this point in the history
  • Loading branch information
kramlex committed Nov 25, 2022
1 parent 04afe5d commit 6393d2b
Show file tree
Hide file tree
Showing 26 changed files with 716 additions and 27 deletions.
9 changes: 9 additions & 0 deletions mvvm-state-core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
id("kmp-library-convention")
id("detekt-convention")
id("publication-convention")
}
2 changes: 2 additions & 0 deletions mvvm-state-core/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="dev.icerock.moko.mvvm.state.core" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm

@Deprecated(
message = "deprecated due to package renaming",
replaceWith = ReplaceWith("ResourceState", "dev.icerock.moko.mvvm.state"),
level = DeprecationLevel.WARNING
)
sealed class ResourceState<out T, out E> {
data class Success<out T, out E>(val data: T) : ResourceState<T, E>()
data class Failed<out T, out E>(val error: E) : ResourceState<T, E>()
class Loading<out T, out E> : ResourceState<T, E>()
class Empty<out T, out E> : ResourceState<T, E>()

fun isLoading(): Boolean = this is Loading
fun isSuccess(): Boolean = this is Success
fun isEmpty(): Boolean = this is Empty
fun isFailed(): Boolean = this is Failed

fun dataValue(): T? = (this as? Success)?.data

fun errorValue(): E? = (this as? Failed)?.error
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm

@Deprecated(
message = "deprecated due to package renaming",
replaceWith = ReplaceWith("asState", "dev.icerock.moko.mvvm.state"),
level = DeprecationLevel.WARNING
)
fun <T, E> T.asState(): ResourceState<T, E> =
ResourceState.Success(this)

@Deprecated(
message = "deprecated due to package renaming",
replaceWith = ReplaceWith("asState", "dev.icerock.moko.mvvm.state"),
level = DeprecationLevel.WARNING
)
fun <T, E> T?.asState(whenNull: () -> ResourceState<T, E>): ResourceState<T, E> =
this?.asState() ?: whenNull()

@Deprecated(
message = "deprecated due to package renaming",
replaceWith = ReplaceWith("asState", "dev.icerock.moko.mvvm.state"),
level = DeprecationLevel.WARNING
)
fun <T, E> List<T>.asState(): ResourceState<List<T>, E> = if (this.isEmpty()) {
ResourceState.Empty()
} else {
ResourceState.Success(this)
}

@Deprecated(
message = "deprecated due to package renaming",
replaceWith = ReplaceWith("asState", "dev.icerock.moko.mvvm.state"),
level = DeprecationLevel.WARNING
)
fun <T, E> List<T>?.asState(whenNull: () -> ResourceState<List<T>, E>): ResourceState<List<T>, E> =
this?.asState() ?: whenNull()

@Deprecated(
message = "deprecated due to package renaming",
replaceWith = ReplaceWith("nullAsEmpty", "dev.icerock.moko.mvvm.state"),
level = DeprecationLevel.WARNING
)
inline fun <reified T, reified E> ResourceState<T, E>?.nullAsEmpty(): ResourceState<T, E> =
this ?: ResourceState.Empty()

@Deprecated(
message = "deprecated due to package renaming",
replaceWith = ReplaceWith("nullAsLoading", "dev.icerock.moko.mvvm.state"),
level = DeprecationLevel.WARNING
)
inline fun <reified T, reified E> ResourceState<T, E>?.nullAsLoading(): ResourceState<T, E> =
this ?: ResourceState.Loading()
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/*
* Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm
package dev.icerock.moko.mvvm.state

sealed class ResourceState<out T, out E> {
data class Success<out T, out E>(val data: T) : ResourceState<T, E>()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/*
* Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm
package dev.icerock.moko.mvvm.state

fun <T, E> T.asState(): ResourceState<T, E> =
ResourceState.Success(this)
Expand Down
19 changes: 19 additions & 0 deletions mvvm-state-flow/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
id("kmp-library-convention")
id("detekt-convention")
id("publication-convention")
}

dependencies {
commonMainApi(projects.mvvmStateCore)

commonMainApi(libs.coroutines)

commonTestApi(libs.mokoTest)
commonTestApi(libs.coroutines)
commonTestApi(projects.mvvmTest)
}
2 changes: 2 additions & 0 deletions mvvm-state-flow/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="dev.icerock.moko.mvvm.state.flow" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm.state.flow

import dev.icerock.moko.mvvm.state.ResourceState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map

fun <T, E> Flow<ResourceState<T, E>>.isSuccessState(): Flow<Boolean> = map { it.isSuccess() }

fun <T, E> Flow<ResourceState<T, E>>.isLoadingState(): Flow<Boolean> = map { it.isLoading() }

fun <T, E> Flow<ResourceState<T, E>>.isErrorState(): Flow<Boolean> = map { it.isFailed() }

fun <T, E> Flow<ResourceState<T, E>>.isEmptyState(): Flow<Boolean> = map { it.isEmpty() }

inline fun <T, E, reified ST : ResourceState<T, E>, FL : Flow<ST>> List<FL>.isSuccessState(): Flow<Boolean> =
combine(this) { list ->
list.firstOrNull { it !is ResourceState.Success<*, *> } == null
}

inline fun <T, E, reified ST : ResourceState<T, E>, FL : Flow<ST>> List<FL>.isLoadingState(): Flow<Boolean> =
combine(this) { list ->
list.firstOrNull() { it is ResourceState.Loading<*, *> } != null
}

inline fun <T, E, reified ST : ResourceState<T, E>, FL : Flow<ST>> List<FL>.isErrorState(): Flow<Boolean> =
combine(this) { list ->
list.firstOrNull { it is ResourceState.Failed<*, *> } != null
}

inline fun <T, E, reified ST : ResourceState<T, E>, FL : Flow<ST>> List<FL>.isEmptyState(): Flow<Boolean> =
combine(this) { list ->
list.firstOrNull { it is ResourceState.Empty<*, *> } != null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm.state.flow

import dev.icerock.moko.mvvm.state.ResourceState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.lastOrNull
import kotlinx.coroutines.flow.map

fun <T, E> Flow<ResourceState<T, E>>.data(): Flow<T?> = map { it.dataValue() }

fun <T, E> StateFlow<ResourceState<T, E>>.dataValue(): T? = value.dataValue()

suspend fun <T, E> Flow<ResourceState<T, E>>.dataValue(): T? =
lastOrNull()?.dataValue()

fun <T, E> Flow<ResourceState<T, E>>.error(): Flow<E?> = map { it.errorValue() }

fun <T, E> StateFlow<ResourceState<T, E>>.errorValue(): E? = value.errorValue()

suspend fun <T, E> Flow<ResourceState<T, E>>.errorValue(): E? =
lastOrNull()?.errorValue()

inline fun <T, E, reified ST : ResourceState<T, E>, FL : Flow<ST>> List<FL>.error(): Flow<E?> =
combine(this) { list ->
@Suppress("UNCHECKED_CAST")
val errorItem = list.firstOrNull {
(it is ResourceState.Failed<*, *>)
} as? ResourceState.Failed<T, E>
errorItem?.error
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm.state.flow

import dev.icerock.moko.mvvm.state.ResourceState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine

fun <T1, E, T2, OT> Flow<ResourceState<T1, E>>.concatData(
flow: Flow<ResourceState<T2, E>>,
function: (T1, T2) -> OT
): Flow<ResourceState<OT, E>> = combine(
this, flow
) { firstState, secondState ->
when {
(firstState is ResourceState.Loading || secondState is ResourceState.Loading) -> ResourceState.Loading()
(firstState is ResourceState.Failed) -> ResourceState.Failed(firstState.error)
(secondState is ResourceState.Failed) -> ResourceState.Failed(secondState.error)
(firstState is ResourceState.Empty || secondState is ResourceState.Empty) -> ResourceState.Empty()
(firstState is ResourceState.Success && secondState is ResourceState.Success) -> ResourceState.Success(
function(
firstState.data,
secondState.data
)
)
else -> ResourceState.Empty()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)

package dev.icerock.moko.mvvm.state.flow

import dev.icerock.moko.mvvm.state.ResourceState
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map

fun <IT, E, OT> Flow<ResourceState<IT, E>>.dataTransform(
transform: Flow<IT>.() -> Flow<OT>
): Flow<ResourceState<OT, E>> = flatMapLatest { state ->
when (state) {
is ResourceState.Success -> transform.invoke(flowOf(state.data))
.map { ResourceState.Success(it) }
is ResourceState.Empty -> flowOf(ResourceState.Empty())
is ResourceState.Failed -> flowOf(ResourceState.Failed(state.error))
is ResourceState.Loading -> flowOf(ResourceState.Loading())
}
}

fun <T, IE, OE> Flow<ResourceState<T, IE>>.errorTransform(
transform: Flow<IE>.() -> Flow<OE>
): Flow<ResourceState<T, OE>> = flatMapLatest { state ->
when (state) {
is ResourceState.Success -> flowOf(ResourceState.Success(state.data))
is ResourceState.Loading -> flowOf(ResourceState.Loading())
is ResourceState.Empty -> flowOf(ResourceState.Empty())
is ResourceState.Failed -> transform.invoke(flowOf(state.error))
.map { ResourceState.Failed(it) }
}
}

fun <T, E> Flow<ResourceState<T, E>>.emptyAsError(
errorBuilder: () -> E
): Flow<ResourceState<T, E>> = map {
when (it) {
is ResourceState.Empty -> ResourceState.Failed(errorBuilder())
else -> it
}
}

fun <T, E> Flow<ResourceState<T, E>>.emptyAsData(
dataBuilder: () -> T
): Flow<ResourceState<T, E>> = map {
when (it) {
is ResourceState.Empty -> ResourceState.Success(dataBuilder())
else -> it
}
}

fun <T, E> Flow<ResourceState<T, E>>.emptyIf(
emptyPredicate: (T) -> Boolean
): Flow<ResourceState<T, E>> = map {
when {
it is ResourceState.Success && emptyPredicate(it.data) -> ResourceState.Empty()
else -> it
}
}
Loading

0 comments on commit 6393d2b

Please sign in to comment.