-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.jeanbarrossilva.loadable.list | ||
|
||
/** Converts this [Array] into a [SerializableList]. **/ | ||
fun <T> Array<out T>.serialize(): SerializableList<T> { | ||
return serializableListOf(*this) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,6 @@ | ||
package com.jeanbarrossilva.loadable.list | ||
|
||
import java.io.Serializable | ||
|
||
/** | ||
* Serializes the given [Collection] by converting it into a [SerializableList]. | ||
* | ||
* @see Serializable | ||
**/ | ||
/** Converts this [Collection] it into a [SerializableList]. **/ | ||
inline fun <reified T> Collection<T>.serialize(): SerializableList<T> { | ||
return serializableListOf(*toTypedArray()) | ||
return toTypedArray().serialize() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package com.jeanbarrossilva.loadable.list | ||
|
||
import java.io.Serializable | ||
|
||
/** Scope through which [ListLoadable]s are sent. **/ | ||
abstract class ListLoadableScope<T : Serializable?> internal constructor() { | ||
/** Sends a [ListLoadable.Loading]. **/ | ||
suspend fun load() { | ||
send(ListLoadable.Loading()) | ||
} | ||
|
||
/** | ||
* Sends a [ListLoadable] that matches the given [content]. | ||
* | ||
* @param content [Array] to be converted into a [SerializableList] and sent either as a | ||
* [ListLoadable.Empty] or a [ListLoadable.Populated]. | ||
* @see Array.serialize | ||
**/ | ||
suspend fun load(vararg content: T) { | ||
load(content.serialize()) | ||
} | ||
|
||
/** | ||
* Sends a [ListLoadable] that matches the given [content]. | ||
* | ||
* @param content [SerializableList] to be sent either as a [ListLoadable.Empty] or a | ||
* [ListLoadable.Populated]. | ||
**/ | ||
suspend fun load(content: SerializableList<T>) { | ||
send(content.toListLoadable()) | ||
} | ||
|
||
/** | ||
* Sends a [ListLoadable.Failed]. | ||
* | ||
* @param error [Throwable] to be set as the [ListLoadable.Failed.error]. | ||
**/ | ||
suspend fun fail(error: Throwable) { | ||
send(ListLoadable.Failed(error)) | ||
} | ||
|
||
/** | ||
* Sends the given [listLoadable]. | ||
* | ||
* @param listLoadable [ListLoadable] to be sent. | ||
**/ | ||
protected abstract suspend fun send(listLoadable: ListLoadable<T>) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.jeanbarrossilva.loadable.list.flow | ||
|
||
import com.jeanbarrossilva.loadable.list.ListLoadable | ||
import com.jeanbarrossilva.loadable.list.ListLoadableScope | ||
import java.io.Serializable | ||
import kotlinx.coroutines.flow.FlowCollector | ||
|
||
/** | ||
* [ListLoadableScope] that emits sent [ListLoadable]s to the given [collector]. | ||
* | ||
* @param collector [FlowCollector] to which sent [ListLoadable]s will be emitted. | ||
**/ | ||
internal class FlowCollectorListLoadableScope<T : Serializable?>( | ||
private val collector: FlowCollector<ListLoadable<T>> | ||
) : ListLoadableScope<T>() { | ||
override suspend fun send(listLoadable: ListLoadable<T>) { | ||
collector.emit(listLoadable) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.jeanbarrossilva.loadable.list.flow | ||
|
||
import com.jeanbarrossilva.loadable.list.ListLoadable | ||
import com.jeanbarrossilva.loadable.list.SerializableList | ||
import com.jeanbarrossilva.loadable.list.serializableListOf | ||
import com.jeanbarrossilva.loadable.list.toListLoadable | ||
import java.io.Serializable | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
|
||
/** Creates a [MutableStateFlow] with a [ListLoadable.Loading] as its initial value. **/ | ||
fun <T : Serializable?> listLoadableFlow(): MutableStateFlow<ListLoadable<T>> { | ||
return MutableStateFlow(ListLoadable.Loading()) | ||
} | ||
|
||
/** | ||
* Returns a [MutableStateFlow] whose [value][MutableStateFlow.value] is a [ListLoadable] that | ||
* matches the given [content]. | ||
* | ||
* @param content [Array] from which the [ListLoadable] will be created. | ||
**/ | ||
fun <T : Serializable?> listLoadableFlowOf(vararg content: T): MutableStateFlow<ListLoadable<T>> { | ||
return listLoadableFlowOf(serializableListOf(*content)) | ||
} | ||
|
||
/** | ||
* Returns a [MutableStateFlow] whose [value][MutableStateFlow.value] is a [ListLoadable] that | ||
* matches the given [content]. | ||
* | ||
* @param content [SerializableList] from which the [ListLoadable] will be created. | ||
**/ | ||
fun <T : Serializable?> listLoadableFlowOf(content: SerializableList<T>): | ||
MutableStateFlow<ListLoadable<T>> { | ||
return MutableStateFlow(content.toListLoadable()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package com.jeanbarrossilva.loadable.list.flow | ||
|
||
import com.jeanbarrossilva.loadable.Loadable | ||
import com.jeanbarrossilva.loadable.flow.loadable | ||
import com.jeanbarrossilva.loadable.list.ListLoadable | ||
import com.jeanbarrossilva.loadable.list.ListLoadableScope | ||
import com.jeanbarrossilva.loadable.list.SerializableList | ||
import com.jeanbarrossilva.loadable.list.toListLoadable | ||
import java.io.Serializable | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.SharingStarted | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import kotlinx.coroutines.flow.emitAll | ||
import kotlinx.coroutines.flow.map | ||
import kotlinx.coroutines.flow.stateIn | ||
import kotlinx.coroutines.launch | ||
|
||
/** | ||
* Maps each emission to a [ListLoadable] and emits an initial [loading][ListLoadable.Loading] | ||
* value. | ||
* | ||
* @param coroutineScope [CoroutineScope] in which the [StateFlow] will be started and its | ||
* [value][StateFlow.value] will be shared. | ||
* @param sharingStarted Strategy for controlling when sharing starts and ends. | ||
**/ | ||
fun <T : Serializable?> Flow<SerializableList<T>>.listLoadable( | ||
coroutineScope: CoroutineScope, | ||
sharingStarted: SharingStarted | ||
): StateFlow<ListLoadable<T>> { | ||
return loadable().map(Loadable<SerializableList<T>>::toListLoadable).stateIn( | ||
coroutineScope, | ||
sharingStarted, | ||
initialValue = ListLoadable.Loading() | ||
) | ||
} | ||
|
||
/** | ||
* Creates a [StateFlow] of [ListLoadable]s that's started and shared in the [coroutineScope] and | ||
* emits them through [load] with a [ListLoadableScope]. Its initial [value][StateFlow.value] is | ||
* [loading][ListLoadable.Loading]. | ||
* | ||
* @param coroutineScope [CoroutineScope] in which the resulting [StateFlow] will be started and its | ||
* value will be shared. | ||
* @param load Operations to be made on the [ListLoadableScope] responsible for emitting | ||
* [ListLoadable]s sent to it to the created [StateFlow]. | ||
**/ | ||
fun <T : Serializable?> listLoadableFlow( | ||
coroutineScope: CoroutineScope, | ||
load: suspend ListLoadableScope<T>.() -> Unit | ||
): StateFlow<ListLoadable<T>> { | ||
return listLoadableFlow<T>() | ||
.apply { | ||
coroutineScope.launch { | ||
emitAll(emptyListLoadableFlow(load)) | ||
} | ||
} | ||
.asStateFlow() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.jeanbarrossilva.loadable.list | ||
|
||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
|
||
internal class ArrayExtensionsTests { | ||
@Test | ||
fun `GIVEN an Array WHEN serializing it THEN it's a SerializableList with all of its previous elements`() { // ktlint-disable max-line-length | ||
Check failure on line 8 in loadable-list/src/test/java/com/jeanbarrossilva/loadable/list/ArrayExtensionsTests.kt GitHub Actions / ktlint
Check failure on line 8 in loadable-list/src/test/java/com/jeanbarrossilva/loadable/list/ArrayExtensionsTests.kt GitHub Actions / ktlint
|
||
assertEquals(serializableListOf(1, 2, 3), arrayOf(1, 2, 3).serialize()) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,18 @@ import kotlinx.coroutines.flow.map | |
import kotlinx.coroutines.test.runTest | ||
|
||
internal class FlowExtensionsTests { | ||
@OptIn(ExperimentalCoroutinesApi::class) | ||
@Test | ||
fun `GIVEN some content WHEN creating ListLoadable Flow with it THEN it emits Loading followed by the matching ListLoadable`() { // ktlint-disable max-line-length | ||
Check failure on line 23 in loadable-list/src/test/java/com/jeanbarrossilva/loadable/list/flow/FlowExtensionsTests.kt GitHub Actions / ktlint
Check failure on line 23 in loadable-list/src/test/java/com/jeanbarrossilva/loadable/list/flow/FlowExtensionsTests.kt GitHub Actions / ktlint
|
||
runTest { | ||
listLoadableFlow { load(1, 2, 3) }.test { | ||
assertIs<ListLoadable.Loading<Int>>(awaitItem()) | ||
assertEquals(ListLoadable.Populated(serializableListOf(1, 2, 3)), awaitItem()) | ||
awaitComplete() | ||
} | ||
} | ||
} | ||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
@Test | ||
fun `GIVEN a ListLoadable Flow WHEN filtering by non-loading values THEN it's filtered`() { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.jeanbarrossilva.loadable.list.flow | ||
|
||
import app.cash.turbine.test | ||
import com.jeanbarrossilva.loadable.list.ListLoadable | ||
import com.jeanbarrossilva.loadable.list.serializableListOf | ||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
import kotlin.test.assertIs | ||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
import kotlinx.coroutines.test.runTest | ||
|
||
internal class MutableStateFlowExtensionsTests { | ||
@OptIn(ExperimentalCoroutinesApi::class) | ||
@Test | ||
fun `GIVEN a SerializableList WHEN creating a ListLoadable Flow with it THEN it's created`() { | ||
runTest { | ||
listLoadableFlowOf(serializableListOf(1, 2, 3)).test { | ||
assertEquals(ListLoadable.Populated(serializableListOf(1, 2, 3)), awaitItem()) | ||
} | ||
} | ||
} | ||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
@Test | ||
fun `GIVEN an Array WHEN creating a ListLoadable Flow with it THEN it's created`() { | ||
runTest { | ||
listLoadableFlowOf(1, 2, 3).test { | ||
assertEquals(ListLoadable.Populated(serializableListOf(1, 2, 3)), awaitItem()) | ||
} | ||
} | ||
} | ||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
@Test | ||
fun `GIVEN some content WHEN creating a ListLoadable StateFlow with it THEN it emits Loading followed by the matching ListLoadable`() { | ||
Check failure on line 35 in loadable-list/src/test/java/com/jeanbarrossilva/loadable/list/flow/MutableStateFlowExtensionsTests.kt GitHub Actions / ktlint
|
||
runTest { | ||
listLoadableFlow(this) { load(1, 2, 3) }.test { | ||
assertIs<ListLoadable.Loading<Int>>(awaitItem()) | ||
assertEquals(ListLoadable.Populated(serializableListOf(1, 2, 3)), awaitItem()) | ||
} | ||
} | ||
} | ||
} |