mirror of
https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture.git
synced 2026-04-20 16:01:24 +02:00
WIP: feature/abstractions (#45)
* Abstraction layer backup * Removed DataEntity, was unnecessary for now * Separated network, persistence, entities and interaction, closes #29 * Renamed binding * Removed build files, example tests Removed build files, example tests * Fixed build files were not being ignored all around app * Updated CI ymls * Small changes * Fixed legacy repository package names * Fixed CQ findings * Updated Fastlane * Packaging changes and version upgrades * Removed core from interactors * Version bumps * Added new module graph
This commit is contained in:
1
abstractions/.gitignore
vendored
Normal file
1
abstractions/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
17
abstractions/build.gradle
Normal file
17
abstractions/build.gradle
Normal file
@@ -0,0 +1,17 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: "de.mannodermaus.android-junit5"
|
||||
|
||||
apply from: "$rootProject.projectDir/scripts/module.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation libraries.kotlin
|
||||
|
||||
testImplementation testLibraries.jUnitApi
|
||||
testImplementation testLibraries.mockk
|
||||
testImplementation testLibraries.kluent
|
||||
|
||||
testRuntimeOnly testLibraries.jUnitEngine
|
||||
}
|
||||
0
abstractions/consumer-rules.pro
Normal file
0
abstractions/consumer-rules.pro
Normal file
21
abstractions/proguard-rules.pro
vendored
Normal file
21
abstractions/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
3
abstractions/src/main/AndroidManifest.xml
Normal file
3
abstractions/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.melih.abstractions" />
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.melih.abstractions.data
|
||||
|
||||
interface ViewEntity
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.melih.abstractions.deliverable
|
||||
|
||||
abstract class Reason : Throwable() {
|
||||
|
||||
abstract val messageRes: Int
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.melih.abstractions.deliverable
|
||||
|
||||
|
||||
/**
|
||||
* Result class that wraps any [Success], [Failure] or [State]
|
||||
*/
|
||||
sealed class Result<out T>
|
||||
|
||||
//region Subclasses
|
||||
|
||||
class Success<out T>(val successData: T) : Result<T>()
|
||||
class Failure(val errorData: Reason) : Result<Nothing>()
|
||||
|
||||
sealed class State : Result<Nothing>() {
|
||||
class Loading : State()
|
||||
class Loaded : State()
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Extensions
|
||||
|
||||
inline fun <T> Result<T>.handle(
|
||||
stateBlock: (State) -> Unit,
|
||||
failureBlock: (Reason) -> Unit,
|
||||
successBlock: (T) -> Unit
|
||||
) {
|
||||
when (this) {
|
||||
is Success -> successBlock(successData)
|
||||
is Failure -> failureBlock(errorData)
|
||||
is State -> stateBlock(this)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> Result<T>.onSuccess(successBlock: (T) -> Unit): Result<T> {
|
||||
if (this is Success)
|
||||
successBlock(successData)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun <T> Result<T>.onFailure(errorBlock: (Reason) -> Unit): Result<T> {
|
||||
if (this is Failure)
|
||||
errorBlock(errorData)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun <T> Result<T>.onState(stateBlock: (State) -> Unit): Result<T> {
|
||||
if (this is State)
|
||||
stateBlock(this)
|
||||
|
||||
return this
|
||||
}
|
||||
//endregion
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.melih.abstractions.mapper
|
||||
|
||||
import com.melih.abstractions.data.ViewEntity
|
||||
|
||||
abstract class Mapper<in T, out R : ViewEntity> {
|
||||
|
||||
abstract fun convert(t: T): R
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.melih.abstractions
|
||||
|
||||
import com.melih.abstractions.deliverable.Failure
|
||||
import com.melih.abstractions.deliverable.Reason
|
||||
import com.melih.abstractions.deliverable.State
|
||||
import com.melih.abstractions.deliverable.Success
|
||||
import com.melih.abstractions.deliverable.handle
|
||||
import io.mockk.called
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import org.amshove.kluent.shouldBeInstanceOf
|
||||
import org.amshove.kluent.shouldEqualTo
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
|
||||
class ResultTest {
|
||||
|
||||
private val number = 10
|
||||
|
||||
private val success = Success(number)
|
||||
private val failure = Failure(object : Reason() {
|
||||
override val messageRes: Int
|
||||
get() = 10
|
||||
|
||||
})
|
||||
private val state = State.Loading()
|
||||
|
||||
private val emptyStateBlock = spyk({ _: State -> })
|
||||
private val emptyFailureBlock = spyk({ _: Reason -> })
|
||||
private val emptySuccessBlock = spyk({ _: Int -> })
|
||||
|
||||
@Test
|
||||
fun `Success should only invoke successBlock with correct data`() {
|
||||
val actualSuccessBlock = spyk({ data: Int ->
|
||||
data shouldEqualTo number
|
||||
Unit
|
||||
})
|
||||
|
||||
success.handle(emptyStateBlock, emptyFailureBlock, actualSuccessBlock)
|
||||
|
||||
verify { emptyStateBlock wasNot called }
|
||||
verify { emptyFailureBlock wasNot called }
|
||||
verify(exactly = 1) { actualSuccessBlock.invoke(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Failure should only invoke failureBlock with correct error`() {
|
||||
val actualFailureBlock = spyk({ reason: Reason ->
|
||||
reason.messageRes shouldEqualTo 10
|
||||
Unit
|
||||
})
|
||||
|
||||
failure.handle(emptyStateBlock, actualFailureBlock, emptySuccessBlock)
|
||||
|
||||
verify { emptySuccessBlock wasNot called }
|
||||
verify { emptyStateBlock wasNot called }
|
||||
verify(exactly = 1) { actualFailureBlock.invoke(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `State should only invoke stateBlock with correct state`() {
|
||||
val actualSuccessBlock = spyk({ state: State ->
|
||||
state shouldBeInstanceOf State.Loading::class
|
||||
Unit
|
||||
})
|
||||
|
||||
state.handle(actualSuccessBlock, emptyFailureBlock, emptySuccessBlock)
|
||||
|
||||
verify { emptySuccessBlock wasNot called }
|
||||
verify { emptyFailureBlock wasNot called }
|
||||
verify(exactly = 1) { actualSuccessBlock.invoke(any()) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user