Skip to content

Commit

Permalink
change: add ListLoadable-Flow-creator functions
Browse files Browse the repository at this point in the history
  • Loading branch information
jeanbarrossilva committed Jul 18, 2023
1 parent 26416b7 commit f9a6d00
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 33 deletions.
4 changes: 2 additions & 2 deletions buildSrc/src/main/java/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ object Versions {
val java = JavaVersion.VERSION_11

object Loadable {
const val CODE = 16
const val NAME = "1.6.3"
const val CODE = 17
const val NAME = "1.6.4"
const val SDK_COMPILE = 33
const val SDK_MIN = 21
const val SDK_TARGET = SDK_COMPILE
Expand Down
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
@@ -1,18 +1,12 @@
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.SerializableList
import com.jeanbarrossilva.loadable.list.toListLoadable
import com.jeanbarrossilva.loadable.list.ListLoadableScope
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.catch
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.flow

/** Returns a [Flow] containing only non-[loading][ListLoadable.Loading] values. **/
fun <T : Serializable?> Flow<ListLoadable<T>>.filterNotLoading(): Flow<ListLoadable<T>> {
Expand All @@ -22,20 +16,34 @@ fun <T : Serializable?> Flow<ListLoadable<T>>.filterNotLoading(): Flow<ListLoada
}

/**
* Maps each emission to a [ListLoadable] and emits an initial [loading][ListLoadable.Loading]
* value.
* Creates a [Flow] of [ListLoadable]s that are emitted through [load] with a [ListLoadableScope]
* and has 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.
* @param load Operations to be made on the [ListLoadableScope] responsible for emitting
* [ListLoadable]s sent to it to the created [Flow].
**/
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()
)
fun <T : Serializable?> listLoadableFlow(load: suspend ListLoadableScope<T>.() -> Unit):
Flow<ListLoadable<T>> {
return emptyListLoadableFlow {
load()
load.invoke(this)
}
}

/**
* Creates a [Flow] of [ListLoadable]s that are emitted through [load] with a [ListLoadableScope].
* Doesn't have any initial value (hence its emptiness).
*
* @param load Operations to be made on the [ListLoadableScope] responsible for emitting
* [ListLoadable]s sent to it to the created [Flow].
**/
internal fun <T : Serializable?> emptyListLoadableFlow(
load: suspend ListLoadableScope<T>.() -> Unit
): Flow<ListLoadable<T>> {
return flow<ListLoadable<T>> {
FlowCollectorListLoadableScope(this).apply {
load.invoke(this)
}
}
.catch { emit(ListLoadable.Failed(it)) }
}
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

View workflow job for this annotation

GitHub Actions / ktlint

[ktlint] reported by reviewdog 🐶 Exceeded max line length (100) Raw Output: loadable-list/src/test/java/com/jeanbarrossilva/loadable/list/ArrayExtensionsTests.kt:8:1: error: Exceeded max line length (100) (standard:max-line-length)

Check failure on line 8 in loadable-list/src/test/java/com/jeanbarrossilva/loadable/list/ArrayExtensionsTests.kt

View workflow job for this annotation

GitHub Actions / ktlint

[ktlint] reported by reviewdog 🐶 Directive 'ktlint-disable' is deprecated. Replace with @Suppress annotation Raw Output: loadable-list/src/test/java/com/jeanbarrossilva/loadable/list/ArrayExtensionsTests.kt:8:116: error: Directive 'ktlint-disable' is deprecated. Replace with @Suppress annotation (internal:ktlint-suppression)
assertEquals(serializableListOf(1, 2, 3), arrayOf(1, 2, 3).serialize())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

View workflow job for this annotation

GitHub Actions / ktlint

[ktlint] reported by reviewdog 🐶 Exceeded max line length (100) Raw Output: loadable-list/src/test/java/com/jeanbarrossilva/loadable/list/flow/FlowExtensionsTests.kt:23:1: error: Exceeded max line length (100) (standard:max-line-length)

Check failure on line 23 in loadable-list/src/test/java/com/jeanbarrossilva/loadable/list/flow/FlowExtensionsTests.kt

View workflow job for this annotation

GitHub Actions / ktlint

[ktlint] reported by reviewdog 🐶 Directive 'ktlint-disable' is deprecated. Replace with @Suppress annotation Raw Output: loadable-list/src/test/java/com/jeanbarrossilva/loadable/list/flow/FlowExtensionsTests.kt:23:137: error: Directive 'ktlint-disable' is deprecated. Replace with @Suppress annotation (internal:ktlint-suppression)
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`() {
Expand Down
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

View workflow job for this annotation

GitHub Actions / ktlint

[ktlint] reported by reviewdog 🐶 Exceeded max line length (100) Raw Output: loadable-list/src/test/java/com/jeanbarrossilva/loadable/list/flow/MutableStateFlowExtensionsTests.kt:35:1: error: Exceeded max line length (100) (standard:max-line-length)
runTest {
listLoadableFlow(this) { load(1, 2, 3) }.test {
assertIs<ListLoadable.Loading<Int>>(awaitItem())
assertEquals(ListLoadable.Populated(serializableListOf(1, 2, 3)), awaitItem())
}
}
}
}

0 comments on commit f9a6d00

Please sign in to comment.