Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(base) #174

Merged
merged 25 commits into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/src/test/java/com/hoc/flowmvi/CheckModulesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package com.hoc.flowmvi
import androidx.lifecycle.SavedStateHandle
import com.hoc.flowmvi.test_utils.TestCoroutineDispatcherRule
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import kotlin.test.Test
import kotlin.time.ExperimentalTime
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand All @@ -26,6 +28,7 @@ class CheckModulesTest : AutoCloseKoinTest() {
SavedStateHandle::class -> {
mockk<SavedStateHandle> {
every { get<Any?>(any()) } returns null
every { setSavedStateProvider(any(), any()) } just runs
}
}
else -> error("Unknown class: $clazz")
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ allprojects {
kotlinOptions {
val version = JavaVersion.VERSION_11.toString()
jvmTarget = version
languageVersion = "1.8"
}
}

Expand Down
31 changes: 31 additions & 0 deletions buildSrc/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
org.gradle.parallel=true

# Enable the Build Cache
org.gradle.caching=true

# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true

# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=false

# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official

# Enable Kotlin incremental compilation
kotlin.incremental=true
Binary file added buildSrc/gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
6 changes: 6 additions & 0 deletions buildSrc/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/deps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ inline val PDsS.kotlinAndroid: PDS get() = id("kotlin-android")
inline val PDsS.kotlin: PDS get() = id("kotlin")
inline val PDsS.kotlinKapt: PDS get() = id("kotlin-kapt")
inline val PDsS.kotlinParcelize: PDS get() = id("kotlin-parcelize")
inline val PDsS.nocopyPlugin: PDS get() = id("dev.ahmedmourad.nocopy.nocopy-gradle-plugin")

inline val DependencyHandler.domain get() = project(":domain")
inline val DependencyHandler.core get() = project(":core")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.hoc.flowmvi.core_ui

import kotlin.coroutines.ContinuationInterceptor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.currentCoroutineContext
import timber.log.Timber

suspend fun debugCheckImmediateMainDispatcher() {
if (BuildConfig.DEBUG) {
val interceptor = currentCoroutineContext()[ContinuationInterceptor]
Timber.d("debugCheckImmediateMainDispatcher: interceptor=$interceptor")

check(interceptor === Dispatchers.Main.immediate) {
"Expected ContinuationInterceptor to be Dispatchers.Main.immediate but was $interceptor"
}
}
}
3 changes: 2 additions & 1 deletion core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ java {
}

dependencies {
implementation(deps.coroutines.core)
api(deps.coroutines.core)
api(deps.arrow.core)
addUnitTest()
}
64 changes: 64 additions & 0 deletions core/src/main/java/com/hoc/flowmvi/core/NonEmptySet.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.hoc.flowmvi.core

/**
* `NonEmptySet` is a data type used to model sets that guarantee to have at least one value.
*/
class NonEmptySet<out T>
@Throws(IllegalArgumentException::class)
private constructor(val set: Set<T>) : AbstractSet<T>() {
init {
require(set.isNotEmpty()) { "Set must not be empty" }
require(set !is NonEmptySet<T>) { "Set must not be NonEmptySet" }
}

override val size: Int get() = set.size
override fun iterator(): Iterator<T> = set.iterator()
override fun isEmpty(): Boolean = false

operator fun plus(l: NonEmptySet<@UnsafeVariance T>): NonEmptySet<T> =
NonEmptySet(set + l.set)

@Suppress("RedundantOverride")
override fun equals(other: Any?): Boolean = super.equals(other)

@Suppress("RedundantOverride")
override fun hashCode(): Int = super.hashCode()

override fun toString(): String =
"NonEmptySet(${set.joinToString()})"

companion object {
/**
* Creates a [NonEmptySet] from the given [Collection].
* @return null if [this] is empty.
*/
@JvmStatic
fun <T> Collection<T>.toNonEmptySetOrNull(): NonEmptySet<T>? =
if (isEmpty()) null else NonEmptySet(toSet())

/**
* Creates a [NonEmptySet] from the given [Set].
* @return null if [this] is empty.
*/
@JvmStatic
fun <T> Set<T>.toNonEmptySetOrNull(): NonEmptySet<T>? = (this as? NonEmptySet<T>)
?: if (isEmpty()) null else NonEmptySet(this)

/**
* Creates a [NonEmptySet] from the given values.
*/
@JvmStatic
fun <T> of(element: T, vararg elements: T): NonEmptySet<T> = NonEmptySet(
buildSet(capacity = 1 + elements.size) {
add(element)
addAll(elements)
}
)

/**
* Creates a [NonEmptySet] that contains only the specified [element].
*/
@JvmStatic
fun <T> of(element: T): NonEmptySet<T> = NonEmptySet(setOf(element))
}
}
22 changes: 22 additions & 0 deletions core/src/main/java/com/hoc/flowmvi/core/ValidatedNes.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.hoc.flowmvi.core

import arrow.core.Validated
import arrow.typeclasses.Semigroup

typealias ValidatedNes<E, A> = Validated<NonEmptySet<E>, A>

@Suppress("NOTHING_TO_INLINE")
inline fun <A> A.validNes(): ValidatedNes<Nothing, A> =
Validated.Valid(this)

@Suppress("NOTHING_TO_INLINE")
inline fun <E> E.invalidNes(): ValidatedNes<E, Nothing> =
Validated.Invalid(NonEmptySet.of(this))

object NonEmptySetSemigroup : Semigroup<NonEmptySet<Any?>> {
override fun NonEmptySet<Any?>.combine(b: NonEmptySet<Any?>): NonEmptySet<Any?> = this + b
}

@Suppress("UNCHECKED_CAST")
fun <T> Semigroup.Companion.nonEmptySet(): Semigroup<NonEmptySet<T>> =
NonEmptySetSemigroup as Semigroup<NonEmptySet<T>>
84 changes: 84 additions & 0 deletions core/src/main/java/com/hoc/flowmvi/core/selfReference.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.hoc.flowmvi.core

import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

// Generic inline classes is an Experimental feature.
// It may be dropped or changed at any time.
// Opt-in is required with the -language-version 1.8 compiler option.
// See https://kotlinlang.org/docs/inline-classes.html for more information.
@JvmInline
value class SelfReference<T>(val value: T) : ReadOnlyProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T = value
}

/**
* A delegate that allows to reference the object itself.
* This is useful to avoid initialization order issues.
*
* This is a alternative way to:
* - `lateinit var` (mutable variables can be modified by mistake).
* - [lazy] (lazy evaluation is unnecessary in this case).
*
* NOTE: Do **NOT** access the value of the return delegate synchronously inside [initializer].
* Eg: `val x: Int by selfReferenced { x + 1 }` is a wrong usage, it will cause an exception.
*
* ### Example
* Below is an example of how to use it:
*
* ```kotlin
* import kotlinx.coroutines.flow.*
* import kotlinx.coroutines.*
*
* class Demo {
* private val trigger = MutableSharedFlow<Unit>()
* private val scope = CoroutineScope(Dispatchers.Default)
*
* val intStateFlow: StateFlow<Int?> by selfReferenced {
* merge(
* flow {
* var c = 0
* while (true) { emit(c++); delay(300) }
* },
* trigger.mapNotNull {
* println("access to $intStateFlow")
* intStateFlow.value?.minus(1)
* }
* )
* .stateIn(scope, SharingStarted.Eagerly, null)
* }
*
* fun trigger() = scope.launch { trigger.emit(Unit) }
* }
*
* fun main(): Unit = runBlocking {
* val demo = Demo()
* val job = demo.intStateFlow
* .onEach(::println)
* .launchIn(this)
*
* delay(1_000)
* demo.trigger()
*
* delay(500)
* demo.trigger()
*
* delay(500)
* job.cancel()
*
* // null
* // 0
* // 1
* // 2
* // 3
* // access to kotlinx.coroutines.flow.ReadonlyStateFlow@2cfeac11
* // 2
* // 4
* // access to kotlinx.coroutines.flow.ReadonlyStateFlow@2cfeac11
* // 3
* // 5
* // 6
* }
* ```
*/
fun <T> selfReferenced(initializer: () -> T) = SelfReference(initializer())
Loading