Skip to content

:mvi-base -> :feature-main #40

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

Merged
merged 14 commits into from
Oct 11, 2021
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: 2 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ android {
}

buildTypes {
getByName("release") {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ buildscript {
}
dependencies {
classpath("com.android.tools.build:gradle:7.0.2")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30")
classpath("com.diffplug.spotless:spotless-plugin-gradle:5.16.0")
classpath("dev.ahmedmourad.nocopy:nocopy-gradle-plugin:1.4.0")
classpath("org.jacoco:org.jacoco.core:0.8.7")
Expand Down
14 changes: 9 additions & 5 deletions buildSrc/src/main/kotlin/deps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,14 @@ inline val DependencyHandler.data get() = project(":data")
inline val DependencyHandler.featureMain get() = project(":feature-main")
inline val DependencyHandler.featureAdd get() = project(":feature-add")
inline val DependencyHandler.featureSearch get() = project(":feature-search")
inline val DependencyHandler.mviBase get() = project(":mvi-base")
inline val DependencyHandler.mviTesting get() = project(":mvi-testing")

fun DependencyHandler.addUnitTest() {
add("testImplementation", deps.test.junit)
add("testImplementation", deps.test.mockk)
add("testImplementation", deps.test.kotlinJUnit)
add("testImplementation", deps.coroutines.test)
fun DependencyHandler.addUnitTest(testImplementation: Boolean = true) {
val configName = if (testImplementation) "testImplementation" else "implementation"

add(configName, deps.test.junit)
add(configName, deps.test.mockk)
add(configName, deps.test.kotlinJUnit)
add(configName, deps.coroutines.test)
}
3 changes: 2 additions & 1 deletion core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ android {
targetSdk = appConfig.targetSdkVersion

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
getByName("release") {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/com/hoc/flowmvi/core/Extensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.hoc.flowmvi.core

@Suppress("unused")
inline val Any?.unit
get() = Unit
3 changes: 2 additions & 1 deletion data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ android {
targetSdk = appConfig.targetSdkVersion

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
getByName("release") {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
Expand Down
4 changes: 3 additions & 1 deletion feature-add/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ android {
targetSdk = appConfig.targetSdkVersion

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
getByName("release") {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
Expand All @@ -40,6 +41,7 @@ android {
dependencies {
implementation(domain)
implementation(core)
implementation(mviBase)

implementation(deps.androidx.appCompat)
implementation(deps.androidx.coreKtx)
Expand Down
5 changes: 4 additions & 1 deletion feature-main/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ android {
targetSdk = appConfig.targetSdkVersion

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
getByName("release") {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
Expand Down Expand Up @@ -47,6 +48,7 @@ android {
dependencies {
implementation(domain)
implementation(core)
implementation(mviBase)

implementation(deps.androidx.appCompat)
implementation(deps.androidx.coreKtx)
Expand All @@ -66,4 +68,5 @@ dependencies {
implementation(deps.flowExt)

addUnitTest()
testImplementation(mviTesting)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.hoc.flowmvi.core.collectIn
import com.hoc.flowmvi.core.navigator.Navigator
import com.hoc.flowmvi.core.refreshes
import com.hoc.flowmvi.core.toast
import com.hoc.flowmvi.mvi_base.MviView
import com.hoc.flowmvi.ui.main.databinding.ActivityMainBinding
import com.hoc081098.viewbindingdelegate.viewBinding
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand All @@ -34,7 +35,9 @@ import org.koin.androidx.viewmodel.ext.android.viewModel

@FlowPreview
@ExperimentalCoroutinesApi
class MainActivity : AppCompatActivity(R.layout.activity_main) {
class MainActivity :
AppCompatActivity(R.layout.activity_main),
MviView<ViewIntent, ViewState, SingleEvent> {
private val mainVM by viewModel<MainVM>()
private val navigator by inject<Navigator>()

Expand Down Expand Up @@ -93,20 +96,19 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
.collectIn(this) { handleSingleEvent(it) }

// pass view intent to view model
intents()
viewIntents()
.onEach { mainVM.processIntent(it) }
.launchIn(lifecycleScope)
}

@Suppress("NOTHING_TO_INLINE")
private inline fun intents(): Flow<ViewIntent> = merge(
override fun viewIntents(): Flow<ViewIntent> = merge(
flowOf(ViewIntent.Initial),
mainBinding.swipeRefreshLayout.refreshes().map { ViewIntent.Refresh },
mainBinding.retryButton.clicks().map { ViewIntent.Retry },
removeChannel.consumeAsFlow().map { ViewIntent.RemoveUser(it) }
)

private fun handleSingleEvent(event: SingleEvent) {
override fun handleSingleEvent(event: SingleEvent) {
Log.d("MainActivity", "handleSingleEvent $event")
return when (event) {
SingleEvent.Refresh.Success -> toast("Refresh success")
Expand All @@ -117,7 +119,7 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
}
}

private fun render(viewState: ViewState) {
override fun render(viewState: ViewState) {
Log.d("MainActivity", "render $viewState")

userAdapter.submitList(viewState.userItems)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.hoc.flowmvi.ui.main

import com.hoc.flowmvi.domain.entity.User
import com.hoc.flowmvi.mvi_base.MviIntent
import com.hoc.flowmvi.mvi_base.MviSingleEvent
import com.hoc.flowmvi.mvi_base.MviViewState

internal data class UserItem(
data class UserItem(
val id: String,
val email: String,
val avatar: String,
Expand All @@ -28,19 +31,19 @@ internal data class UserItem(
)
}

internal sealed interface ViewIntent {
sealed interface ViewIntent : MviIntent {
object Initial : ViewIntent
object Refresh : ViewIntent
object Retry : ViewIntent
data class RemoveUser(val user: UserItem) : ViewIntent
}

internal data class ViewState(
data class ViewState(
val userItems: List<UserItem>,
val isLoading: Boolean,
val error: Throwable?,
val isRefreshing: Boolean
) {
) : MviViewState {
companion object {
fun initial() = ViewState(
userItems = emptyList(),
Expand Down Expand Up @@ -100,7 +103,7 @@ internal sealed interface PartialChange {
}
}

internal sealed interface SingleEvent {
sealed interface SingleEvent : MviSingleEvent {
sealed interface Refresh : SingleEvent {
object Success : Refresh
data class Failure(val error: Throwable) : Refresh
Expand Down
13 changes: 7 additions & 6 deletions feature-main/src/main/java/com/hoc/flowmvi/ui/main/MainVM.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package com.hoc.flowmvi.ui.main
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.hoc.flowmvi.core.unit
import com.hoc.flowmvi.domain.usecase.GetUsersUseCase
import com.hoc.flowmvi.domain.usecase.RefreshGetUsersUseCase
import com.hoc.flowmvi.domain.usecase.RemoveUserUseCase
import com.hoc.flowmvi.mvi_base.MviViewModel
import com.hoc081098.flowext.flatMapFirst
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
Expand Down Expand Up @@ -36,18 +38,17 @@ import kotlinx.coroutines.flow.take
@Suppress("USELESS_CAST")
@FlowPreview
@ExperimentalCoroutinesApi
internal class MainVM(
class MainVM(
private val getUsersUseCase: GetUsersUseCase,
private val refreshGetUsers: RefreshGetUsersUseCase,
private val removeUser: RemoveUserUseCase,
) : ViewModel() {
) : ViewModel(), MviViewModel<ViewIntent, ViewState, SingleEvent> {
private val _eventChannel = Channel<SingleEvent>(Channel.BUFFERED)
private val _intentFlow = MutableSharedFlow<ViewIntent>(extraBufferCapacity = 64)

val viewState: StateFlow<ViewState>
val singleEvent: Flow<SingleEvent> get() = _eventChannel.receiveAsFlow()

fun processIntent(intent: ViewIntent) = _intentFlow.tryEmit(intent)
override val viewState: StateFlow<ViewState>
override val singleEvent: Flow<SingleEvent> get() = _eventChannel.receiveAsFlow()
override fun processIntent(intent: ViewIntent) = _intentFlow.tryEmit(intent).unit

init {
val initialVS = ViewState.initial()
Expand Down
103 changes: 103 additions & 0 deletions feature-main/src/test/java/com/hoc/flowmvi/ui/main/MainContractTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.hoc.flowmvi.ui.main

import com.hoc.flowmvi.domain.entity.User
import kotlin.test.Test
import kotlin.test.assertEquals

class MainContractTest {
@Test
fun test_userItem_equals() {
assertEquals(
UserItem(
id = "0",
email = "[email protected]",
avatar = "avatar.png",
firstName = "first",
lastName = "last"
),
UserItem(
id = "0",
email = "[email protected]",
avatar = "avatar.png",
firstName = "first",
lastName = "last"
)
)
}

@Test
fun test_userItem_hashCode() {
assertEquals(
UserItem(
id = "0",
email = "[email protected]",
avatar = "avatar.png",
firstName = "first",
lastName = "last"
).hashCode(),
UserItem(
id = "0",
email = "[email protected]",
avatar = "avatar.png",
firstName = "first",
lastName = "last"
).hashCode()
)
}

@Test
fun test_userItem_fullName() {
assertEquals(
UserItem(
id = "0",
email = "[email protected]",
avatar = "avatar.png",
firstName = "first",
lastName = "last"
).fullName,
"first last",
)
}

@Test
fun test_userItem_toDomain() {
assertEquals(
UserItem(
id = "0",
email = "[email protected]",
avatar = "avatar.png",
firstName = "first",
lastName = "last"
).toDomain(),
User(
id = "0",
email = "[email protected]",
firstName = "first",
lastName = "last",
avatar = "avatar.png",
)
)
}

@Test
fun test_userItem_fromDomain() {
assertEquals(
UserItem(
domain = User(
id = "0",
email = "[email protected]",
firstName = "first",
lastName = "last",
avatar = "avatar.png",
)
),
UserItem(
id = "0",
email = "[email protected]",
avatar = "avatar.png",
firstName = "first",
lastName = "last"
),
)
}
}
Loading