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

Release/1.3.0 #86

Merged
merged 4 commits into from
May 29, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
uses: ./.github/actions/setup-gradle

- name: Unit Tests ${{ matrix.module }}
run: ./gradlew :${{ matrix.module }}:testDebugUnitTest
run: ./gradlew :${{ matrix.module }}:testDebugUnitTest --no-parallel

- name: Kover ${{ matrix.module }}
run: ./gradlew :${{ matrix.module }}:koverVerify
Expand Down
41 changes: 13 additions & 28 deletions toolkit/event-observer/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,36 +1,21 @@
import com.toolkit.plugin.multiplatform.androidMain
import com.toolkit.plugin.multiplatform.androidUnitTest

plugins {
id("toolkit-multiplatform-library")
id("toolkit-android-library")
id("toolkit-publish")
}

android.namespace = "br.com.arch.toolkit.livedata"

kotlin {
sourceSets {

// Libraries
androidMain {
dependencies {
implementation(libraries.jetbrains.stdlib.jdk8)
implementation(libraries.jetbrains.coroutines.core)
implementation(libraries.jetbrains.coroutines.android)
implementation(libraries.androidx.lifecycle.livedata)
}
}
dependencies {
// Libraries
implementation(libraries.jetbrains.stdlib.jdk8)
implementation(libraries.jetbrains.coroutines.core)
implementation(libraries.jetbrains.coroutines.android)
implementation(libraries.androidx.lifecycle.livedata)

// Test Libraries
androidUnitTest {
dependencies {
implementation(libraries.androidx.lifecycle.livedata)
implementation(libraries.androidx.lifecycle.runtime)
implementation(libraries.androidx.test.core)
implementation(libraries.jetbrains.test.coroutines)
implementation(libraries.mockito.test.core)
implementation(libraries.mockito.test.kotlin)
}
}
}
// Test Libraries
testImplementation(libraries.androidx.lifecycle.runtime)
testImplementation(libraries.androidx.test.core)
testImplementation(libraries.jetbrains.test.coroutines)
testImplementation(libraries.mockito.test.core)
testImplementation(libraries.mockito.test.kotlin)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ data class DataResult<T>(
*/
private var scope: CoroutineScope? = null
fun scope(scope: CoroutineScope) = apply { this.scope = scope }
fun scope(scope: CoroutineDispatcher) = apply { this.scope = CoroutineScope(scope) }

/**
* Scope to run all the transformations, it is optional, is null, will use the default inside ObserveWrapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package br.com.arch.toolkit.result
import androidx.annotation.NonNull
import androidx.annotation.Nullable
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import br.com.arch.toolkit.annotation.Experimental
Expand Down Expand Up @@ -1055,7 +1056,7 @@ class ObserveWrapper<T> internal constructor() {
val observer = object : Observer<DataResult<T>?> {
override fun onChanged(value: DataResult<T>?) {
scope.launchWithErrorTreatment {
handleResult(value)
handleResult(value) { owner.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) }
if (eventList.isEmpty()) {
liveData.removeObserver(this)
}
Expand Down Expand Up @@ -1094,64 +1095,67 @@ class ObserveWrapper<T> internal constructor() {
}
//endregion

private suspend fun handleResult(@Nullable result: DataResult<T>?) {
private suspend fun handleResult(
@Nullable result: DataResult<T>?,
evaluateBeforeDispatch: suspend () -> Boolean = { true }
) {
if (result == null) return
val isLoading = result.status == LOADING

eventList.iterate(result) { event ->
return@iterate when {
// Handle None
result.status == NONE -> (event as? NoneEvent)?.wrapper
?.handle(null, transformDispatcher) == true
?.handle(null, transformDispatcher, evaluateBeforeDispatch) == true

// Handle Loading
event is LoadingEvent -> event.run {
wrapper.handle(isLoading, transformDispatcher) && isLoading.not()
wrapper.handle(isLoading, transformDispatcher, evaluateBeforeDispatch) && isLoading.not()
}

// Handle ShowLoading
event is ShowLoadingEvent && isLoading -> event.run {
wrapper.handle(true, transformDispatcher)
wrapper.handle(true, transformDispatcher, evaluateBeforeDispatch)
}

// Handle HideLoading
event is HideLoadingEvent && isLoading.not() -> event.run {
wrapper.handle(isLoading, transformDispatcher)
wrapper.handle(isLoading, transformDispatcher, evaluateBeforeDispatch)
}

// Handle Error
event is ErrorEvent && result.status == ERROR -> event.run {
wrapper.handle(result.error, transformDispatcher)
wrapper.handle(result.error, transformDispatcher, evaluateBeforeDispatch)
}

// Handle Success
event is SuccessEvent && result.status == SUCCESS -> event.run {
wrapper.handle(null, transformDispatcher)
wrapper.handle(null, transformDispatcher, evaluateBeforeDispatch)
}

// Handle Data
event is DataEvent -> (event as DataEvent<T>).wrapper.let {
it.handle(result.data, transformDispatcher) && (result.data != null)
it.handle(result.data, transformDispatcher, evaluateBeforeDispatch) && (result.data != null)
}

// Handle Empty
event is EmptyEvent && result.isListType && result.isEmpty -> event.run {
wrapper.handle(null, transformDispatcher)
wrapper.handle(null, transformDispatcher, evaluateBeforeDispatch)
}

// Handle Not Empty
event is NotEmptyEvent && result.isListType && result.isNotEmpty -> event.run {
wrapper.handle(null, transformDispatcher)
wrapper.handle(null, transformDispatcher, evaluateBeforeDispatch)
}

// Handle Result
event is ResultEvent<*> -> (event as ResultEvent<T>).run {
wrapper.handle(result, transformDispatcher)
wrapper.handle(result, transformDispatcher, evaluateBeforeDispatch)
}

// Handle Status
event is StatusEvent -> event.run {
wrapper.handle(result.status, transformDispatcher)
wrapper.handle(result.status, transformDispatcher, evaluateBeforeDispatch)
}

else -> false
Expand Down Expand Up @@ -1231,10 +1235,17 @@ internal class WrapObserver<T, V>(
@Nullable val observer: (suspend (T) -> Unit)? = null,
@Nullable val emptyObserver: (suspend () -> Unit)? = null,
@Nullable val transformer: (suspend (T) -> V)? = null,
@Nullable val transformerObserver: (suspend (V) -> Unit)? = null
@Nullable val transformerObserver: (suspend (V) -> Unit)? = null,
) {

suspend fun handle(@Nullable data: T?, dispatcher: CoroutineDispatcher) = when {
suspend fun handle(
@Nullable data: T?,
dispatcher: CoroutineDispatcher,
evaluate: suspend () -> Boolean
) = when {

evaluate.invoke().not() -> false

emptyObserver != null -> {
emptyObserver.invoke()
true
Expand All @@ -1245,14 +1256,15 @@ internal class WrapObserver<T, V>(
true
}

data != null -> executeTransformer(data, dispatcher)
data != null -> executeTransformer(data, dispatcher, evaluate)

else -> false
}

private suspend fun executeTransformer(
@Nullable data: T,
dispatcher: CoroutineDispatcher
dispatcher: CoroutineDispatcher,
evaluate: suspend () -> Boolean
) = when {
transformerObserver == null -> false
transformer == null -> false
Expand All @@ -1263,15 +1275,20 @@ internal class WrapObserver<T, V>(

val catch = CoroutineExceptionHandler { _, error -> throw error }
withContext(currentCoroutineContext() + catch) {
result.onSuccess { transformerObserver.invoke(it) }
.onFailure {
if (evaluate.invoke()) {
result.onSuccess {
transformerObserver.invoke(it)
}.onFailure {
throw DataResultTransformationException(
"Error performing transformation",
it
)
}
true
} else {
false
}
}
true
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,15 @@ internal fun <T> ResponseLiveData<T>.testSetValue(value: DataResult<T>?) {
it.invoke(this, value)
}
}

internal fun enableExceptionCheck() {
Class.forName("kotlinx.coroutines.test.TestScopeKt")
.getDeclaredMethod("setCatchNonTestRelatedExceptions", Boolean::class.java)
.invoke(null, false)
}

internal fun disableExceptionCheck() {
Class.forName("kotlinx.coroutines.test.TestScopeKt")
.getDeclaredMethod("setCatchNonTestRelatedExceptions", Boolean::class.java)
.invoke(null, true)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,25 @@

package br.com.arch.toolkit.result

import br.com.arch.toolkit.disableExceptionCheck
import br.com.arch.toolkit.enableExceptionCheck
import br.com.arch.toolkit.exception.DataResultException
import br.com.arch.toolkit.exception.DataResultTransformationException
import br.com.arch.toolkit.util.dataResultError
import br.com.arch.toolkit.util.dataResultSuccess
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
import org.junit.runners.MethodSorters
import org.mockito.kotlin.any
Expand All @@ -25,26 +32,36 @@ import org.mockito.kotlin.verifyBlocking
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.whenever

/**
* Yes I do hate the person who did the coroutines stopping treating exceptions inside the scope of the test
* without giving me any other option to do that
*/
@OptIn(ExperimentalCoroutinesApi::class)
@Ignore("this test can only be run manually, because it will only work if it's the first test to run.")
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class ObserveWrapperTest_ThrownError {

private val error = IllegalStateException("Thrown Error!")
private val expected = DataResultException(
message = "Any error event found, please add one error { ... } to retry",
error = error
message = "Any error event found, please add one error { ... } to retry", error = error
)
private val expectedTransformation = DataResultTransformationException(
message = "Error performing transformation",
error = error
message = "Error performing transformation", error = error
)
private val expectedError = DataResultException(
message = "Error retried but without any success",
error = error
message = "Error retried but without any success", error = error
)

init {
Dispatchers.setMain(StandardTestDispatcher())
@Before
fun setUp() {
Dispatchers.setMain(StandardTestDispatcher(TestCoroutineScheduler()))
enableExceptionCheck()
}

@After
fun tearDown() {
Dispatchers.resetMain()
disableExceptionCheck()
}

//region Data Error Thrown Scenarios
Expand Down
34 changes: 17 additions & 17 deletions tools/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ build-sdk-target = "34"
build-tools = "34.0.0"

## JetBrains
jetbrains-kotlin = "1.9.20"
jetbrains-coroutines = "1.7.3" # Quebra a parte de tratamento de exception no ObserveWrapper
jetbrains-kotlin = "1.9.23"
jetbrains-coroutines = "1.8.0" # Quebra a parte de tratamento de exception no ObserveWrapper
jetbrains-kover = "0.7.4"

## AndroidX
androidx-appcompat = "1.6.1"
androidx-annotation = "1.7.1"
androidx-annotation = "1.8.0"
androidx-constraint = "2.2.0-alpha13"
androidx-recyclerview = "1.3.2"
androidx-lifecycle-livedata = "2.7.0"
androidx-lifecycle-runtime = "2.7.0"
androidx-lifecycle-livedata = "2.8.0"
androidx-lifecycle-runtime = "2.8.0"
androidx-window = "1.2.0"
androidx-splash = "1.0.1"
androidx-test-core = "2.2.0"
Expand All @@ -37,12 +37,12 @@ androidx-test-espresso = "3.5.1"

## AndroidX Compose
androidx-compose-compiler = "1.4.3"
androidx-compose-material = "1.6.2"
androidx-compose-material3 = "1.2.0"
androidx-compose-material = "1.6.7"
androidx-compose-material3 = "1.2.1"
androidx-compose-constraint = "1.0.1"
androidx-compose-core = "1.6.2"
androidx-compose-lifecycle = "2.7.0"
androidx-compose-activity = "1.8.2"
androidx-compose-core = "1.6.7"
androidx-compose-lifecycle = "2.8.0"
androidx-compose-activity = "1.9.0"

## SquareUp
square-retrofit = "2.9.0"
Expand All @@ -51,8 +51,8 @@ square-moshi = "1.14.0"
square-timber = "5.0.1"

## Google
google-material = "1.11.0"
google-ksp = "1.9.10-1.0.13"
google-material = "1.12.0"
google-ksp = "1.9.23-1.0.20"

## Tools
mockito = "5.6.0"
Expand Down Expand Up @@ -119,16 +119,16 @@ detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-form
# ------------------------------------------------------------------------------------------------ #
## Comment this when you start to update some libraries
x-normalize-001 = { group = "androidx.activity", name = "activity", version.ref = "androidx-compose-activity" }
x-normalize-002 = { group = "androidx.annotation", name = "annotation-experimental", version = "1.4.0" }
x-normalize-002 = { group = "androidx.annotation", name = "annotation-experimental", version = "1.4.1" }
x-normalize-003 = { group = "androidx.arch.core", name = "core-common", version = "2.2.0" }
x-normalize-004 = { group = "androidx.arch.core", name = "core-runtime", version = "2.2.0" }
x-normalize-005 = { group = "androidx.collection", name = "collection", version = "1.4.0" }
x-normalize-006 = { group = "androidx.core", name = "core", version = "1.12.0" }
x-normalize-007 = { group = "androidx.core", name = "core-ktx", version = "1.12.0" }
x-normalize-006 = { group = "androidx.core", name = "core", version = "1.13.1" }
x-normalize-007 = { group = "androidx.core", name = "core-ktx", version = "1.13.1" }
x-normalize-008 = { group = "androidx.customview", name = "customview", version = "1.1.0" }
x-normalize-009 = { group = "androidx.drawerlayout", name = "drawerlayout", version = "1.2.0" }
x-normalize-010 = { group = "androidx.lifecycle", name = "lifecycle-common", version = "2.7.0" }
x-normalize-011 = { group = "androidx.viewpager2", name = "viewpager2", version = "1.1.0-beta02" }
x-normalize-010 = { group = "androidx.lifecycle", name = "lifecycle-common", version = "2.8.0" }
x-normalize-011 = { group = "androidx.viewpager2", name = "viewpager2", version = "1.1.0" }
x-normalize-012 = { group = "androidx.test", name = "monitor", version = "1.6.1" }
x-normalize-013 = { group = "androidx.test", name = "runner", version = "1.5.2" }
x-normalize-014 = { group = "com.google.auto.value", name = "auto-value-annotations", version = "1.9" }
Expand Down
Loading