From 14b13a684a266226828a7a15f1e0127ff3da60c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adriel=20Caf=C3=A9?= Date: Sun, 11 Oct 2020 14:24:06 -0300 Subject: [PATCH] refactor: replace state machine (#52) * refactor: replace state machine * fix: contributing web url * fix: add missing ViewModel inheritance * fix: tests --- buildSrc/src/main/java/dependencies.kt | 7 +- .../dialetus/domain/regions/RegionsService.kt | 3 + presentation/build.gradle | 1 + .../contributing/ContributingInteraction.kt | 6 +- .../contributing/ContributingNavigation.kt | 14 +- .../dialetus/presentation/di/injection.kt | 15 +- .../presentation/dialects/DialectsActivity.kt | 103 +++++----- .../dialects/DialectsViewModel.kt | 74 ++----- .../dialects/dialect_interactions.kt | 9 - .../dialetus/presentation/ktx/dialect.kt | 9 + .../jcaique/dialetus/presentation/ktx/view.kt | 6 + .../presentation/regions/RegionsActivity.kt | 73 ++++--- .../presentation/regions/RegionsViewModel.kt | 39 ++-- .../presentation/common/flowTesting.kt | 38 ---- .../presentation/common/suspendableUtils.kt | 51 ----- .../dataflow/CommandsProcessorTests.kt | 86 -------- .../dataflow/StateMachineTests.kt | 189 ------------------ .../regions/RegionsViewModelTest.kt | 103 ++++------ .../dialetus/utils/dataflow/CommandTrigger.kt | 17 -- .../utils/dataflow/CommandsProcessor.kt | 24 --- .../ConfigChangesAwareStateContainer.kt | 39 ---- .../dialetus/utils/dataflow/StateContainer.kt | 27 --- .../dialetus/utils/dataflow/StateMachine.kt | 57 ------ .../utils/dataflow/StateTransition.kt | 26 --- .../dialetus/utils/dataflow/TaskExecutor.kt | 30 --- .../dataflow/UnsupportedUserInteraction.kt | 5 - .../utils/dataflow/UserInteraction.kt | 6 - .../dialetus/utils/dataflow/ViewCommand.kt | 3 - .../dialetus/utils/dataflow/ViewState.kt | 14 -- .../utils/dataflow/statesPropagation.kt | 18 -- .../dialetus/utils/extensions/di_ktx.kt | 20 -- 31 files changed, 187 insertions(+), 925 deletions(-) delete mode 100644 presentation/src/main/java/com/jcaique/dialetus/presentation/dialects/dialect_interactions.kt create mode 100644 presentation/src/main/java/com/jcaique/dialetus/presentation/ktx/dialect.kt create mode 100644 presentation/src/main/java/com/jcaique/dialetus/presentation/ktx/view.kt delete mode 100644 presentation/src/test/java/com/jcaique/dialetus/presentation/common/flowTesting.kt delete mode 100644 presentation/src/test/java/com/jcaique/dialetus/presentation/common/suspendableUtils.kt delete mode 100644 presentation/src/test/java/com/jcaique/dialetus/presentation/dataflow/CommandsProcessorTests.kt delete mode 100644 presentation/src/test/java/com/jcaique/dialetus/presentation/dataflow/StateMachineTests.kt delete mode 100644 utils/src/main/java/com/jcaique/dialetus/utils/dataflow/CommandTrigger.kt delete mode 100644 utils/src/main/java/com/jcaique/dialetus/utils/dataflow/CommandsProcessor.kt delete mode 100644 utils/src/main/java/com/jcaique/dialetus/utils/dataflow/ConfigChangesAwareStateContainer.kt delete mode 100644 utils/src/main/java/com/jcaique/dialetus/utils/dataflow/StateContainer.kt delete mode 100644 utils/src/main/java/com/jcaique/dialetus/utils/dataflow/StateMachine.kt delete mode 100644 utils/src/main/java/com/jcaique/dialetus/utils/dataflow/StateTransition.kt delete mode 100644 utils/src/main/java/com/jcaique/dialetus/utils/dataflow/TaskExecutor.kt delete mode 100644 utils/src/main/java/com/jcaique/dialetus/utils/dataflow/UnsupportedUserInteraction.kt delete mode 100644 utils/src/main/java/com/jcaique/dialetus/utils/dataflow/UserInteraction.kt delete mode 100644 utils/src/main/java/com/jcaique/dialetus/utils/dataflow/ViewCommand.kt delete mode 100644 utils/src/main/java/com/jcaique/dialetus/utils/dataflow/ViewState.kt delete mode 100644 utils/src/main/java/com/jcaique/dialetus/utils/dataflow/statesPropagation.kt diff --git a/buildSrc/src/main/java/dependencies.kt b/buildSrc/src/main/java/dependencies.kt index 5166870..708041a 100644 --- a/buildSrc/src/main/java/dependencies.kt +++ b/buildSrc/src/main/java/dependencies.kt @@ -30,13 +30,14 @@ object Versions { const val assertJ29 = "2.9.1" const val espresso = "3.1.0" const val androidJUnit = "1.1.0" - const val mockitoKT = "2.0.0-RC1" - const val mockito = "2.19.0" + const val mockitoKT = "2.2.0" + const val mockito = "3.5.13" const val kodeinDI = "6.0.1" const val slf4j = "1.7.25" const val fabric = "1.26.1" const val coroutine = "1.3.2" + const val dalek = "1.0.2" } object Dependencies { @@ -88,6 +89,8 @@ object Dependencies { val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutine}" val coroutinesAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutine}" val coroutinesTest = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.coroutine}" + + val dalek = "com.github.adrielcafe:dalek:${Versions.dalek}" } object BuildPlugins { diff --git a/domain/src/main/java/com/jcaique/dialetus/domain/regions/RegionsService.kt b/domain/src/main/java/com/jcaique/dialetus/domain/regions/RegionsService.kt index ab899e9..3adf41f 100644 --- a/domain/src/main/java/com/jcaique/dialetus/domain/regions/RegionsService.kt +++ b/domain/src/main/java/com/jcaique/dialetus/domain/regions/RegionsService.kt @@ -1,8 +1,11 @@ package com.jcaique.dialetus.domain.regions +import com.jcaique.dialetus.domain.errors.GatewayIntegrationIssues +import com.jcaique.dialetus.domain.errors.UnexpectedResponse import com.jcaique.dialetus.domain.models.Region interface RegionsService { + @Throws(UnexpectedResponse::class, GatewayIntegrationIssues::class) suspend fun fetchRegions(): List } diff --git a/presentation/build.gradle b/presentation/build.gradle index 3f94df3..f2edfaf 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -9,6 +9,7 @@ dependencies { implementation Dependencies.coroutines implementation Dependencies.coroutinesAndroid + implementation Dependencies.dalek AndroidModule.main.forEach { implementation it } AndroidModule.unitTesting.forEach { testImplementation it } diff --git a/presentation/src/main/java/com/jcaique/dialetus/presentation/contributing/ContributingInteraction.kt b/presentation/src/main/java/com/jcaique/dialetus/presentation/contributing/ContributingInteraction.kt index 722b12e..e74eefa 100644 --- a/presentation/src/main/java/com/jcaique/dialetus/presentation/contributing/ContributingInteraction.kt +++ b/presentation/src/main/java/com/jcaique/dialetus/presentation/contributing/ContributingInteraction.kt @@ -1,6 +1,6 @@ package com.jcaique.dialetus.presentation.contributing -internal interface ContributingInteraction { - object OpenAndroid : ContributingInteraction - object OpenWeb : ContributingInteraction +internal sealed class ContributingInteraction(val url: String) { + object OpenAndroid : ContributingInteraction(ContributingConst.ANDROID) + object OpenWeb : ContributingInteraction(ContributingConst.API) } diff --git a/presentation/src/main/java/com/jcaique/dialetus/presentation/contributing/ContributingNavigation.kt b/presentation/src/main/java/com/jcaique/dialetus/presentation/contributing/ContributingNavigation.kt index 3cbfa89..6a91dd8 100644 --- a/presentation/src/main/java/com/jcaique/dialetus/presentation/contributing/ContributingNavigation.kt +++ b/presentation/src/main/java/com/jcaique/dialetus/presentation/contributing/ContributingNavigation.kt @@ -8,7 +8,6 @@ import android.view.View import android.view.ViewGroup import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.jcaique.dialetus.presentation.R -import com.jcaique.dialetus.utils.dataflow.UnsupportedUserInteraction import kotlinx.android.synthetic.main.regions_menu_view.* internal class ContributingNavigation : BottomSheetDialogFragment() { @@ -33,17 +32,8 @@ internal class ContributingNavigation : BottomSheetDialogFragment() { } } - private fun handleClick(interaction: ContributingInteraction) { - when (interaction) { - ContributingInteraction.OpenAndroid -> navigate( - ContributingConst.ANDROID - ) - ContributingInteraction.OpenWeb -> navigate( - ContributingConst.API - ) - else -> throw UnsupportedUserInteraction - } - } + private fun handleClick(interaction: ContributingInteraction) = + navigate(interaction.url) private fun navigate(url: String) { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) diff --git a/presentation/src/main/java/com/jcaique/dialetus/presentation/di/injection.kt b/presentation/src/main/java/com/jcaique/dialetus/presentation/di/injection.kt index bab9cc1..6015555 100644 --- a/presentation/src/main/java/com/jcaique/dialetus/presentation/di/injection.kt +++ b/presentation/src/main/java/com/jcaique/dialetus/presentation/di/injection.kt @@ -1,12 +1,7 @@ package com.jcaique.dialetus.presentation.di -import com.jcaique.dialetus.presentation.dialects.DialectsPresentation import com.jcaique.dialetus.presentation.dialects.DialectsViewModel -import com.jcaique.dialetus.presentation.regions.RegionsPresentation import com.jcaique.dialetus.presentation.regions.RegionsViewModel -import com.jcaique.dialetus.utils.KodeinTags -import com.jcaique.dialetus.utils.extensions.newStateContainer -import com.jcaique.dialetus.utils.extensions.newStateMachine import org.kodein.di.Kodein import org.kodein.di.generic.bind import org.kodein.di.generic.instance @@ -15,21 +10,13 @@ import org.kodein.di.generic.provider val presentationModule = Kodein.Module(name = "presentation") { bind() from provider { - val stateContainer = newStateContainer(KodeinTags.hostActivity) - val stateMachine = newStateMachine(stateContainer) - RegionsViewModel( - service = instance(), - machine = stateMachine + service = instance() ) } bind() from provider { - val stateContainer = newStateContainer(KodeinTags.hostActivity) - val stateMachine = newStateMachine(stateContainer) - DialectsViewModel( - machine = stateMachine, service = instance() ) } diff --git a/presentation/src/main/java/com/jcaique/dialetus/presentation/dialects/DialectsActivity.kt b/presentation/src/main/java/com/jcaique/dialetus/presentation/dialects/DialectsActivity.kt index 3680a56..aae7543 100644 --- a/presentation/src/main/java/com/jcaique/dialetus/presentation/dialects/DialectsActivity.kt +++ b/presentation/src/main/java/com/jcaique/dialetus/presentation/dialects/DialectsActivity.kt @@ -9,11 +9,17 @@ import androidx.core.view.isVisible import androidx.core.widget.addTextChangedListener import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager +import cafe.adriel.dalek.DalekEvent +import cafe.adriel.dalek.Failure +import cafe.adriel.dalek.Finish +import cafe.adriel.dalek.Start +import cafe.adriel.dalek.Success +import cafe.adriel.dalek.collectIn import com.jcaique.dialetus.domain.models.Dialect import com.jcaique.dialetus.domain.models.Region import com.jcaique.dialetus.presentation.R import com.jcaique.dialetus.presentation.contributing.ContributingConst -import com.jcaique.dialetus.utils.dataflow.ViewState +import com.jcaique.dialetus.presentation.ktx.value import com.jcaique.dialetus.utils.extensions.selfInject import com.jcaique.dialetus.utils.extensions.share import com.jcaique.dialetus.utils.ui.DividerItemDecoration @@ -22,16 +28,14 @@ import kotlinx.android.synthetic.main.activity_regions.emptyStateView import kotlinx.android.synthetic.main.activity_regions.errorStateView import kotlinx.android.synthetic.main.activity_regions.loadingStateView import kotlinx.android.synthetic.main.error_state_layout.* -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch import org.kodein.di.KodeinAware import org.kodein.di.generic.instance class DialectsActivity : AppCompatActivity(), KodeinAware { - + companion object { const val EXTRA_REGION = "region" - + fun newInstance(activity: Activity, region: Region) = activity.run { startActivity( Intent(this, DialectsActivity::class.java) @@ -41,73 +45,68 @@ class DialectsActivity : AppCompatActivity(), KodeinAware { } override val kodein = selfInject() + private val viewModel by kodein.instance() - + private val region by lazy { intent.getSerializableExtra(EXTRA_REGION) as Region } - + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_dialects) - init() - setupToolbar() + setupViews() + showDialects() } - private fun init() { - viewModel.handle(ShowDialects(region)) - - lifecycleScope.launch { - viewModel.bind().collect { handle(it) } + private fun setupViews() { + dialectsToolBar.apply { + title = region.name.capitalize() + setNavigationOnClickListener { finish() } } - + dialectsList.run { layoutManager = LinearLayoutManager(this@DialectsActivity) addItemDecoration( DividerItemDecoration(this@DialectsActivity) - .also { + .also { ContextCompat .getDrawable(this@DialectsActivity, R.drawable.divider) - ?.let(it::setDrawable) + ?.let(it::setDrawable) } ) } - + dialectsFilter.addTextChangedListener { filterDialects() } } - - private fun setupToolbar() { - dialectsToolBar.apply { - title = region.name.capitalize() - setNavigationOnClickListener { finish() } - } - } - private fun handle(state: ViewState) { - controlVisibilities(state) + private fun showDialects() { + viewModel + .getDialects(region) + .collectIn(lifecycleScope, ::handleResult) + } - when (state) { - is ViewState.Success -> setupContent(state.value) - is ViewState.Failed -> setupRetry() - } + private fun filterDialects() { + viewModel + .filterDialects(query = dialectsFilter.value) + .collectIn(lifecycleScope, ::handleResult) } - private fun setupRetry() { - errorStateView.let { - tryAgainBtn.setOnClickListener { - viewModel.handle(ShowDialects(region)) - } + private suspend fun handleResult(event: DalekEvent) { + controlVisibilities(event) + + when (event) { + is Success -> setupContent(event.value) + is Failure -> setupRetry() } } - private fun setupContent(value: DialectsPresentation) { - dialectsList.adapter = - DialectAdapter(value, ::shareDialect) + private fun setupContent(presentation: DialectsPresentation) { + dialectsList.adapter = DialectAdapter(presentation, ::shareDialect) } - - private fun filterDialects() { - dialectsFilter.text - ?.toString() - ?.let(::FilterDialects) - ?.let(viewModel::handle) + + private fun setupRetry() { + tryAgainBtn.setOnClickListener { + showDialects() + } } private fun shareDialect(dialect: Dialect) = dialect.run { @@ -126,11 +125,13 @@ class DialectsActivity : AppCompatActivity(), KodeinAware { .trimMargin() .share(this@DialectsActivity) } - - private fun controlVisibilities(state: ViewState) { - loadingStateView.isVisible = state is ViewState.Loading - emptyStateView.isVisible = state is ViewState.Success && state.value.dialects.isEmpty() - errorStateView.isVisible = state is ViewState.Failed - dialectsList.isVisible = state is ViewState.Success && state.value.dialects.isNotEmpty() + + private fun controlVisibilities(event: DalekEvent) { + if (event is Finish) return + + loadingStateView.isVisible = event is Start + emptyStateView.isVisible = event is Success && event.value.dialects.isEmpty() + errorStateView.isVisible = event is Failure + dialectsList.isVisible = event is Success && event.value.dialects.isNotEmpty() } } diff --git a/presentation/src/main/java/com/jcaique/dialetus/presentation/dialects/DialectsViewModel.kt b/presentation/src/main/java/com/jcaique/dialetus/presentation/dialects/DialectsViewModel.kt index 6b9a0c6..2004f8c 100644 --- a/presentation/src/main/java/com/jcaique/dialetus/presentation/dialects/DialectsViewModel.kt +++ b/presentation/src/main/java/com/jcaique/dialetus/presentation/dialects/DialectsViewModel.kt @@ -1,65 +1,33 @@ package com.jcaique.dialetus.presentation.dialects +import androidx.lifecycle.ViewModel +import cafe.adriel.dalek.Dalek +import cafe.adriel.dalek.DalekEvent import com.jcaique.dialetus.domain.dialects.DialectsService import com.jcaique.dialetus.domain.models.Dialect -import com.jcaique.dialetus.utils.dataflow.StateMachine -import com.jcaique.dialetus.utils.dataflow.StateTransition -import com.jcaique.dialetus.utils.dataflow.UnsupportedUserInteraction -import com.jcaique.dialetus.utils.dataflow.UserInteraction -import com.jcaique.dialetus.utils.extensions.normalize -import kotlinx.coroutines.coroutineScope +import com.jcaique.dialetus.domain.models.Region +import com.jcaique.dialetus.presentation.ktx.matches +import kotlinx.coroutines.flow.Flow internal class DialectsViewModel( - private val machine: StateMachine, private val service: DialectsService -) { +) : ViewModel() { + // TODO cache dialects elsewhere private var dialects = emptyList() - - fun bind() = machine.states() - - fun handle(interaction: UserInteraction) { - interpret(interaction) - .let(machine::consume) - } - - private fun interpret(interaction: UserInteraction) = - when (interaction) { - is ShowDialects -> StateTransition( - ::showDialects, - interaction - ) - is FilterDialects -> StateTransition( - ::filterDialects, - interaction - ) - else -> throw UnsupportedUserInteraction + + fun getDialects(region: Region): Flow> = + Dalek { + service + .getDialectsBy(region.name.toLowerCase()) + .also(::dialects::set) + .let(::DialectsPresentation) } - - private suspend fun showDialects( - parameters: StateTransition.Parameters - ): DialectsPresentation = coroutineScope { - val interaction = parameters as ShowDialects - - service - .getDialectsBy(interaction.region.name.toLowerCase()) - .let(::DialectsPresentation) - } - - private suspend fun filterDialects( - parameters: StateTransition.Parameters - ): DialectsPresentation = coroutineScope { - val interaction = parameters as FilterDialects - dialects - .filter { - it.dialect - .normalize() - .contains( - other = interaction.query.normalize(), - ignoreCase = true - ) - } - .let(::DialectsPresentation) - } + fun filterDialects(query: String): Flow> = + Dalek { + dialects + .filter { it.matches(query) } + .let(::DialectsPresentation) + } } diff --git a/presentation/src/main/java/com/jcaique/dialetus/presentation/dialects/dialect_interactions.kt b/presentation/src/main/java/com/jcaique/dialetus/presentation/dialects/dialect_interactions.kt deleted file mode 100644 index fd069ab..0000000 --- a/presentation/src/main/java/com/jcaique/dialetus/presentation/dialects/dialect_interactions.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.jcaique.dialetus.presentation.dialects - -import com.jcaique.dialetus.domain.models.Region -import com.jcaique.dialetus.utils.dataflow.StateTransition -import com.jcaique.dialetus.utils.dataflow.UserInteraction - -data class ShowDialects(val region: Region) : UserInteraction, StateTransition.Parameters - -data class FilterDialects(val query: String) : UserInteraction, StateTransition.Parameters diff --git a/presentation/src/main/java/com/jcaique/dialetus/presentation/ktx/dialect.kt b/presentation/src/main/java/com/jcaique/dialetus/presentation/ktx/dialect.kt new file mode 100644 index 0000000..875893a --- /dev/null +++ b/presentation/src/main/java/com/jcaique/dialetus/presentation/ktx/dialect.kt @@ -0,0 +1,9 @@ +package com.jcaique.dialetus.presentation.ktx + +import com.jcaique.dialetus.domain.models.Dialect +import com.jcaique.dialetus.utils.extensions.normalize + +internal fun Dialect.matches(query: String): Boolean = + dialect + .normalize() + .contains(other = query.normalize(), ignoreCase = true) diff --git a/presentation/src/main/java/com/jcaique/dialetus/presentation/ktx/view.kt b/presentation/src/main/java/com/jcaique/dialetus/presentation/ktx/view.kt new file mode 100644 index 0000000..b5fa2c9 --- /dev/null +++ b/presentation/src/main/java/com/jcaique/dialetus/presentation/ktx/view.kt @@ -0,0 +1,6 @@ +package com.jcaique.dialetus.presentation.ktx + +import android.widget.EditText + +internal val EditText.value: String + get() = text?.toString().orEmpty() diff --git a/presentation/src/main/java/com/jcaique/dialetus/presentation/regions/RegionsActivity.kt b/presentation/src/main/java/com/jcaique/dialetus/presentation/regions/RegionsActivity.kt index 025978a..53cc2ee 100644 --- a/presentation/src/main/java/com/jcaique/dialetus/presentation/regions/RegionsActivity.kt +++ b/presentation/src/main/java/com/jcaique/dialetus/presentation/regions/RegionsActivity.kt @@ -5,22 +5,20 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager +import cafe.adriel.dalek.DalekEvent +import cafe.adriel.dalek.Failure +import cafe.adriel.dalek.Finish +import cafe.adriel.dalek.Start +import cafe.adriel.dalek.Success +import cafe.adriel.dalek.collectIn import com.jcaique.dialetus.domain.models.Region import com.jcaique.dialetus.presentation.R import com.jcaique.dialetus.presentation.contributing.ContributingNavigation import com.jcaique.dialetus.presentation.dialects.DialectsActivity -import com.jcaique.dialetus.utils.dataflow.UserInteraction.OpenedScreen -import com.jcaique.dialetus.utils.dataflow.UserInteraction.RequestedFreshContent -import com.jcaique.dialetus.utils.dataflow.ViewState -import com.jcaique.dialetus.utils.dataflow.ViewState.Failed -import com.jcaique.dialetus.utils.dataflow.ViewState.Loading -import com.jcaique.dialetus.utils.dataflow.ViewState.Success import com.jcaique.dialetus.utils.extensions.selfInject import com.jcaique.dialetus.utils.ui.DividerItemDecoration import kotlinx.android.synthetic.main.activity_regions.* import kotlinx.android.synthetic.main.error_state_layout.* -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch import org.kodein.di.KodeinAware import org.kodein.di.generic.instance @@ -32,15 +30,16 @@ class RegionsActivity : AppCompatActivity(), KodeinAware { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_regions) - init() - setupBottomBar() + setupViews() + showRegions() } - private fun init() { - viewModel.handle(OpenedScreen) + private fun setupViews() { + setSupportActionBar(bar) + val bottom = ContributingNavigation() - lifecycleScope.launch { - viewModel.bind().collect { handle(it) } + bar.setNavigationOnClickListener { + bottom.show(supportFragmentManager, bottom.tag) } regionsRv.run { @@ -49,42 +48,38 @@ class RegionsActivity : AppCompatActivity(), KodeinAware { } } - private fun setupBottomBar() { - setSupportActionBar(bar) - val bottom = ContributingNavigation() - - bar.setNavigationOnClickListener { - bottom.show(supportFragmentManager, bottom.tag) - } + private fun showRegions() { + viewModel + .showRegions() + .collectIn(lifecycleScope, ::handleResult) } - private fun handle(state: ViewState) { - controlVisibilities(state) + private suspend fun handleResult(event: DalekEvent) { + controlVisibilities(event) - when (state) { - is Success -> setupContent(state.value) - is Failed -> setupRetry() + when (event) { + is Success -> setupContent(event.value) + is Failure -> setupRetry() } } + private fun setupContent(value: RegionsPresentation) { + regionsRv.adapter = RegionAdapter(value, ::navigateToDialects) + } + private fun setupRetry() { - errorStateView.let { - tryAgainBtn.setOnClickListener { - viewModel.handle(RequestedFreshContent) - } + tryAgainBtn.setOnClickListener { + showRegions() } } - private fun setupContent(value: RegionsPresentation) { - regionsRv.adapter = - RegionAdapter(value, ::navigateToDialects) - } + private fun controlVisibilities(event: DalekEvent) { + if (event is Finish) return - private fun controlVisibilities(state: ViewState) { - loadingStateView.isVisible = state is Loading - emptyStateView.isVisible = state is Success && state.value.regions.isEmpty() - errorStateView.isVisible = state is Failed - regionsRv.isVisible = state is Success && state.value.regions.isNotEmpty() + loadingStateView.isVisible = event is Start + emptyStateView.isVisible = event is Success && event.value.regions.isEmpty() + errorStateView.isVisible = event is Failure + regionsRv.isVisible = event is Success && event.value.regions.isNotEmpty() } private fun navigateToDialects(region: Region) = diff --git a/presentation/src/main/java/com/jcaique/dialetus/presentation/regions/RegionsViewModel.kt b/presentation/src/main/java/com/jcaique/dialetus/presentation/regions/RegionsViewModel.kt index d2492ef..4b3e300 100644 --- a/presentation/src/main/java/com/jcaique/dialetus/presentation/regions/RegionsViewModel.kt +++ b/presentation/src/main/java/com/jcaique/dialetus/presentation/regions/RegionsViewModel.kt @@ -1,35 +1,20 @@ package com.jcaique.dialetus.presentation.regions +import androidx.lifecycle.ViewModel +import cafe.adriel.dalek.Dalek +import cafe.adriel.dalek.DalekEvent import com.jcaique.dialetus.domain.regions.RegionsService -import com.jcaique.dialetus.utils.dataflow.StateMachine -import com.jcaique.dialetus.utils.dataflow.StateTransition -import com.jcaique.dialetus.utils.dataflow.UnsupportedUserInteraction -import com.jcaique.dialetus.utils.dataflow.UserInteraction +import kotlinx.coroutines.flow.Flow internal class RegionsViewModel( - private val service: RegionsService, - private val machine: StateMachine -) { + private val service: RegionsService +) : ViewModel() { - fun bind() = machine.states() - - fun handle(interaction: UserInteraction) { - interpret(interaction) - .let(machine::consume) - } - - private fun interpret(interaction: UserInteraction) = - when (interaction) { - UserInteraction.OpenedScreen, - UserInteraction.RequestedFreshContent -> StateTransition( - ::showRegions - ) - else -> throw UnsupportedUserInteraction + fun showRegions(): Flow> = + Dalek { + service + .fetchRegions() + .map { it.copy(it.name.capitalize()) } + .let(::RegionsPresentation) } - - private suspend fun showRegions() = - service - .fetchRegions() - .map { it.copy(it.name.capitalize()) } - .let(::RegionsPresentation) } diff --git a/presentation/src/test/java/com/jcaique/dialetus/presentation/common/flowTesting.kt b/presentation/src/test/java/com/jcaique/dialetus/presentation/common/flowTesting.kt deleted file mode 100644 index 41b7e71..0000000 --- a/presentation/src/test/java/com/jcaique/dialetus/presentation/common/flowTesting.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.jcaique.dialetus.presentation.common - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking - -fun Flow.test(scope: CoroutineScope = GlobalScope, block: FlowTest.() -> Unit) { - val emissions = mutableListOf() - val job = scope.launch { toList(emissions) } - FlowTest(job, emissions).apply(block) -} - -class FlowTest(private val parentJob: Job, private val emissions: List) { - - fun triggerEmissions(action: suspend () -> Job) { - runBlocking { action().join() } - } - - fun afterCollect(verification: (List) -> Unit) { - parentJob.invokeOnCompletion { - verification.invoke(emissions) - } - } - - companion object { - fun flowTest( - target: Flow, - scope: CoroutineScope = GlobalScope, - block: FlowTest.() -> Unit - ) { - target.test(scope, block) - } - } -} diff --git a/presentation/src/test/java/com/jcaique/dialetus/presentation/common/suspendableUtils.kt b/presentation/src/test/java/com/jcaique/dialetus/presentation/common/suspendableUtils.kt deleted file mode 100644 index e89f7dc..0000000 --- a/presentation/src/test/java/com/jcaique/dialetus/presentation/common/suspendableUtils.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.jcaique.dialetus.presentation.common - -import kotlin.properties.Delegates -import kotlinx.coroutines.runBlocking - -fun unwrapError(result: Result<*>) = - result.exceptionOrNull() - ?.let { it } - ?: throw IllegalArgumentException("Not an error") - -class SuspendableErrorChecker { - - private var result: Result by Delegates.notNull() - private var error: Throwable? = null - private var target: T? = null - - fun take(block: suspend () -> T) { - result = runBlocking { - runCatching { - target = block() - target!! - } - } - } - - fun once(block: suspend (T) -> Any) { - runBlocking { - target - ?.let { tryExecution(block, it) } - ?: throw IllegalArgumentException("Must provide take{...} block") - } - } - - fun check(assert: (Throwable) -> Unit) { - error - ?.let { assert.invoke(it) } - ?: throw IllegalArgumentException("Must provide once{...} block") - } - - private suspend fun tryExecution(block: suspend (T) -> Any, it: T) = - try { - block(it) - } catch (incoming: Throwable) { - error = incoming - } - - companion object { - fun errorOnSuspendable(block: SuspendableErrorChecker.() -> Unit) = - SuspendableErrorChecker().apply(block) - } -} diff --git a/presentation/src/test/java/com/jcaique/dialetus/presentation/dataflow/CommandsProcessorTests.kt b/presentation/src/test/java/com/jcaique/dialetus/presentation/dataflow/CommandsProcessorTests.kt deleted file mode 100644 index 06dbbe9..0000000 --- a/presentation/src/test/java/com/jcaique/dialetus/presentation/dataflow/CommandsProcessorTests.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.jcaique.dialetus.presentation.dataflow - -import com.jcaique.dialetus.presentation.common.CoroutinesTestHelper -import com.jcaique.dialetus.presentation.common.test -import com.jcaique.dialetus.utils.dataflow.CommandTrigger -import com.jcaique.dialetus.utils.dataflow.CommandsProcessor -import com.jcaique.dialetus.utils.dataflow.TaskExecutor -import com.jcaique.dialetus.utils.dataflow.ViewCommand -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine -import org.assertj.core.api.Assertions.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test - -internal class CommandsProcessorTests { - - @get:Rule - val helper = CoroutinesTestHelper() - - private lateinit var processor: CommandsProcessor - - @Before - fun `before each test`() { - processor = CommandsProcessor( - executor = TaskExecutor.Synchronous(helper.scope) - ) - } - - @Test - fun `should trigger commands`() { - with(processor) { - commands().test { - - triggerEmissions { - process( - CommandTrigger(::generateCommand) - ) - } - - triggerEmissions { - process( - CommandTrigger( - ::generateCommand, - Interaction - ) - ) - } - - afterCollect { emissions -> - - val commands = listOf( - Finish, - FinishWithMessage( - MESSAGE - ) - ) - - assertThat(emissions).isEqualTo(commands) - } - } - } - } - - private suspend fun generateCommand() = - suspendCoroutine { continuation -> - continuation.resume(Finish) - } - - private suspend fun generateCommand(params: CommandTrigger.Parameters) = - suspendCoroutine { continuation -> - val interaction = params as Interaction - continuation.resume( - FinishWithMessage( - MESSAGE - ) - ) - } - - private companion object Interaction : CommandTrigger.Parameters { - const val MESSAGE = "Data from interaction" - } - - object Finish : ViewCommand() - data class FinishWithMessage(val message: String) : ViewCommand() -} diff --git a/presentation/src/test/java/com/jcaique/dialetus/presentation/dataflow/StateMachineTests.kt b/presentation/src/test/java/com/jcaique/dialetus/presentation/dataflow/StateMachineTests.kt deleted file mode 100644 index 147b125..0000000 --- a/presentation/src/test/java/com/jcaique/dialetus/presentation/dataflow/StateMachineTests.kt +++ /dev/null @@ -1,189 +0,0 @@ -package com.jcaique.dialetus.presentation.dataflow - -import com.jcaique.dialetus.presentation.common.CoroutinesTestHelper -import com.jcaique.dialetus.presentation.common.FlowTest.Companion.flowTest -import com.jcaique.dialetus.utils.dataflow.StateContainer -import com.jcaique.dialetus.utils.dataflow.StateMachine -import com.jcaique.dialetus.utils.dataflow.StateTransition -import com.jcaique.dialetus.utils.dataflow.TaskExecutor -import com.jcaique.dialetus.utils.dataflow.ViewState -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine -import org.assertj.core.api.Assertions.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test - -internal class StateMachineTests { - - private lateinit var machine: StateMachine - - @get:Rule - val helper = CoroutinesTestHelper() - - @Before - fun `before each test`() { - machine = StateMachine( - container = StateContainer.Unbounded(helper.scope), - executor = TaskExecutor.Synchronous(helper.scope) - ) - } - - @Test - fun `should generate states, successful execution`() { - - // Given - flowTest(machine.states()) { - - // When - triggerEmissions { - machine.consume( - StateTransition(::successfulExecution) - ) - } - - // Then - afterCollect { emissions -> - - val expectedStates = listOf( - ViewState.FirstLaunch, - ViewState.Loading.FromEmpty, - ViewState.Success(MESSAGE) - ) - - assertThat(emissions).isEqualTo(expectedStates) - } - } - } - - @Test - fun `should generate states, error execution`() { - - // Given - flowTest(machine.states()) { - - // When - triggerEmissions { - machine.consume( - StateTransition(::successfulExecution) - ) - } - - // Then - afterCollect { emissions -> - - val expectedStates = listOf( - ViewState.FirstLaunch, - ViewState.Loading.FromEmpty, - ViewState.Failed(ERROR) - ) - - assertThat(emissions).isEqualTo(expectedStates) - } - } - } - - @Test - fun `should generate states, with previous execution`() { - - // Given - flowTest(machine.states()) { - - // When - triggerEmissions { - machine.consume( - StateTransition(::successfulExecution) - ) - } - - // And - triggerEmissions { - machine.consume( - StateTransition( - ::successfulExecution, - Interaction - ) - ) - } - - // Then - afterCollect { emissions -> - - val expectedStates = listOf( - ViewState.FirstLaunch, - ViewState.Loading.FromEmpty, - ViewState.Success(MESSAGE), - ViewState.Loading.FromPrevious(MESSAGE), - ViewState.Success(Interaction.DATA) - ) - - assertThat(emissions).isEqualTo(expectedStates) - } - } - } - - @Test - fun `should generate states, ignoring previous broken execution`() { - - // Given - flowTest(machine.states()) { - - // When - triggerEmissions { - machine.consume( - StateTransition(::brokenExecution) - ) - } - - // And - triggerEmissions { - machine.consume( - StateTransition(::successfulExecution) - ) - } - - // Then - afterCollect { emissions -> - - val expectedStates = listOf( - ViewState.FirstLaunch, - ViewState.Loading.FromEmpty, - ViewState.Failed(ERROR), - ViewState.Loading.FromEmpty, - ViewState.Success(MESSAGE) - ) - - assertThat(emissions).isEqualTo(expectedStates) - } - } - } - - private suspend fun successfulExecution(): String { - return suspendCoroutine { continuation -> - continuation.resume(MESSAGE) - } - } - - private suspend fun successfulExecution(parameters: StateTransition.Parameters): String { - return suspendCoroutine { continuation -> - val interaction = parameters as Interaction - continuation.resume(Interaction.DATA) - } - } - - private suspend fun brokenExecution(): String { - return suspendCoroutine { continuation -> - continuation.resumeWithException(ERROR) - } - } - - private companion object { - const val MESSAGE = "Kotlin is awesome" - val ERROR = IllegalStateException("Ouch") - } - - object Interaction : StateTransition.Parameters { - const val DATA = "Hello" - } -} diff --git a/presentation/src/test/java/com/jcaique/dialetus/presentation/regions/RegionsViewModelTest.kt b/presentation/src/test/java/com/jcaique/dialetus/presentation/regions/RegionsViewModelTest.kt index c0fefac..ae91515 100644 --- a/presentation/src/test/java/com/jcaique/dialetus/presentation/regions/RegionsViewModelTest.kt +++ b/presentation/src/test/java/com/jcaique/dialetus/presentation/regions/RegionsViewModelTest.kt @@ -1,17 +1,19 @@ package com.jcaique.dialetus.presentation.regions +import cafe.adriel.dalek.Failure +import cafe.adriel.dalek.Finish +import cafe.adriel.dalek.Start +import cafe.adriel.dalek.Success import com.jcaique.dialetus.domain.errors.UnexpectedResponse import com.jcaique.dialetus.domain.models.Region import com.jcaique.dialetus.domain.regions.RegionsService import com.jcaique.dialetus.presentation.common.CoroutinesTestHelper -import com.jcaique.dialetus.presentation.common.FlowTest.Companion.flowTest -import com.jcaique.dialetus.utils.dataflow.StateContainer -import com.jcaique.dialetus.utils.dataflow.StateMachine -import com.jcaique.dialetus.utils.dataflow.TaskExecutor -import com.jcaique.dialetus.utils.dataflow.UserInteraction -import com.jcaique.dialetus.utils.dataflow.ViewState +import com.nhaarman.mockitokotlin2.doReturn +import com.nhaarman.mockitokotlin2.doThrow import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.whenever +import com.nhaarman.mockitokotlin2.stub +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.test.runBlockingTest import org.assertj.core.api.Java6Assertions.assertThat import org.junit.Before import org.junit.Rule @@ -28,81 +30,52 @@ class RegionsViewModelTest { @Before fun `before each test`() { - val stateMachine = StateMachine( - executor = TaskExecutor.Synchronous(helper.scope), - container = StateContainer.Unbounded(helper.scope) - ) - - viewModel = - RegionsViewModel(service, stateMachine) + viewModel = RegionsViewModel(service) } @Test - fun `should report failure when fetching regions from remote`() { - - // Given - flowTest(viewModel.bind()) { - - triggerEmissions { - - // When - whenever(service.fetchRegions()) - .thenAnswer { throw UnexpectedResponse } - - // And - viewModel.handle(UserInteraction.OpenedScreen) - } - - afterCollect { emissions -> + fun `should report failure when fetching regions from remote`() = runBlockingTest { + service.stub { + onBlocking { fetchRegions() } doThrow UnexpectedResponse + } - // Then - val viewStates = listOf( - ViewState.FirstLaunch, - ViewState.Loading.FromEmpty, - ViewState.Failed(UnexpectedResponse) - ) + val expected = listOf( + Start, + Failure(UnexpectedResponse), + Finish + ) + + val result = viewModel + .showRegions() + .toList() - assertThat(emissions).isEqualTo(viewStates) - } - } + assertThat(result).isEqualTo(expected) } @Test - fun `should fetch regions from remote data source with success`() { - - // Given - + fun `should fetch regions from remote data source with success`() = runBlockingTest { val regions = listOf( Region( name = "Baianes", total = 1 ) ) - val presentation = RegionsPresentation(regions) - flowTest(viewModel.bind()) { - - triggerEmissions { - - // When - whenever(service.fetchRegions()).thenReturn(regions) - - // And - viewModel.handle(UserInteraction.OpenedScreen) - } - - afterCollect { emissions -> + service.stub { + onBlocking { fetchRegions() } doReturn regions + } + + val expected = listOf( + Start, + Success(presentation), + Finish + ) - // Then - val viewStates = listOf( - ViewState.FirstLaunch, - ViewState.Loading.FromEmpty, - ViewState.Success(presentation) - ) + val result = viewModel + .showRegions() + .toList() - assertThat(emissions).isEqualTo(viewStates) - } - } + assertThat(result).isEqualTo(expected) } } diff --git a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/CommandTrigger.kt b/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/CommandTrigger.kt deleted file mode 100644 index 2e781a7..0000000 --- a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/CommandTrigger.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.jcaique.dialetus.utils.dataflow - -sealed class CommandTrigger { - - interface Parameters - - class Unparametrized(val task: suspend () -> ViewCommand) : CommandTrigger() - class Parametrized(val task: suspend (Parameters) -> ViewCommand, val parameters: Parameters) : - CommandTrigger() - - companion object { - operator fun invoke(task: suspend () -> ViewCommand) = - Unparametrized(task) - operator fun invoke(task: suspend (Parameters) -> ViewCommand, data: Parameters) = - Parametrized(task, data) - } -} diff --git a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/CommandsProcessor.kt b/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/CommandsProcessor.kt deleted file mode 100644 index 9d4f291..0000000 --- a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/CommandsProcessor.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.jcaique.dialetus.utils.dataflow - -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.ConflatedBroadcastChannel -import kotlinx.coroutines.flow.asFlow - -class CommandsProcessor( - private val executor: TaskExecutor -) { - - @ExperimentalCoroutinesApi - private val broadcaster = ConflatedBroadcastChannel() - - fun commands() = broadcaster.asFlow() - - fun process(trigger: CommandTrigger) = - executor.execute { - val command = when (trigger) { - is CommandTrigger.Unparametrized -> trigger.task.invoke() - is CommandTrigger.Parametrized -> with(trigger) { task.invoke(parameters) } - } - broadcaster.send(command) - } -} diff --git a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/ConfigChangesAwareStateContainer.kt b/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/ConfigChangesAwareStateContainer.kt deleted file mode 100644 index 9c74b27..0000000 --- a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/ConfigChangesAwareStateContainer.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.jcaique.dialetus.utils.dataflow - -import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelProviders -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.channels.ConflatedBroadcastChannel -import kotlinx.coroutines.flow.asFlow - -class ConfigChangesAwareStateContainer : StateContainer, ViewModel() { - - private val broadcaster by lazy { - ConflatedBroadcastChannel>(ViewState.FirstLaunch) - } - - override val emissionScope = viewModelScope - - override fun observableStates() = broadcaster.asFlow() - - override fun current(): ViewState = broadcaster.value - - override suspend fun store(state: ViewState) { - broadcaster.send(state) - } - - companion object { - operator fun invoke(host: FragmentActivity): StateContainer { - - val factory = object : ViewModelProvider.Factory { - override fun create(klass: Class) = - ConfigChangesAwareStateContainer() as Model - } - - val keyClazz = ConfigChangesAwareStateContainer::class.java - return ViewModelProviders.of(host, factory)[keyClazz] as StateContainer - } - } -} diff --git a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/StateContainer.kt b/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/StateContainer.kt deleted file mode 100644 index 34fadf6..0000000 --- a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/StateContainer.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.jcaique.dialetus.utils.dataflow - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.ConflatedBroadcastChannel -import kotlinx.coroutines.flow.asFlow - -interface StateContainer : ViewStateRegistry, - ViewStatesEmitter { - - class Unbounded(scopeToBound: CoroutineScope) : - StateContainer { - - private val broadcaster by lazy { - ConflatedBroadcastChannel>(ViewState.FirstLaunch) - } - - override val emissionScope = scopeToBound - - override fun observableStates() = broadcaster.asFlow() - - override fun current(): ViewState = broadcaster.value - - override suspend fun store(state: ViewState) { - broadcaster.send(state) - } - } -} diff --git a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/StateMachine.kt b/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/StateMachine.kt deleted file mode 100644 index fce188b..0000000 --- a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/StateMachine.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.jcaique.dialetus.utils.dataflow - -class StateMachine( - private val container: StateContainer, - private val executor: TaskExecutor -) { - - fun states() = container.observableStates() - - fun consume(execution: StateTransition) = - executor.execute { - wrapWithStates(execution) - } - - private suspend fun wrapWithStates(execution: StateTransition) { - val first = executionStarted() - moveTo(first) - val next = executeWith(execution) - moveTo(next) - } - - private suspend fun executeWith(transition: StateTransition): ViewState { - return try { - val execution = - when (transition) { - is StateTransition.Unparametrized -> transition.task.invoke() - is StateTransition.Parametrized -> with(transition) { - this.task.invoke( - parameters - ) - } - } - ViewState.Success(execution) - } catch (error: Throwable) { - ViewState.Failed(error) - } - } - - private fun executionStarted() = - when (val state = container.current()) { - is ViewState.FirstLaunch, - is ViewState.Failed -> ViewState.Loading.FromEmpty - else -> restoreIfSuccess(state) - } - - private fun restoreIfSuccess(state: ViewState) = - when (state) { - is ViewState.Success -> ViewState.Loading.FromPrevious( - state.value - ) - else -> state - } - - private suspend fun moveTo(state: ViewState) { - container.store(state) - } -} diff --git a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/StateTransition.kt b/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/StateTransition.kt deleted file mode 100644 index d7d1ef3..0000000 --- a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/StateTransition.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.jcaique.dialetus.utils.dataflow - -sealed class StateTransition { - - interface Parameters - - class Unparametrized internal constructor( - val task: suspend () -> T - ) : StateTransition() - - class Parametrized internal constructor( - val task: suspend (Parameters) -> T, - val parameters: Parameters - ) : StateTransition() - - companion object { - - operator fun invoke(task: suspend () -> T) = - Unparametrized(task) - - operator fun invoke( - task: suspend (Parameters) -> T, - data: Parameters - ) = Parametrized(task, data) - } -} diff --git a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/TaskExecutor.kt b/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/TaskExecutor.kt deleted file mode 100644 index 6df4966..0000000 --- a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/TaskExecutor.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.jcaique.dialetus.utils.dataflow - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking - -interface TaskExecutor { - - fun execute(block: suspend TaskExecutor.() -> Unit): Job - - class Concurrent( - private val scope: CoroutineScope, - private val dispatcher: CoroutineDispatcher - ) : TaskExecutor { - override fun execute(block: suspend TaskExecutor.() -> Unit) = - scope.launch(dispatcher) { - block.invoke(this@Concurrent) - } - } - - class Synchronous(private val scope: CoroutineScope) : - TaskExecutor { - override fun execute(block: suspend TaskExecutor.() -> Unit) = - runBlocking { - scope.launch { block.invoke(this@Synchronous) } - } - } -} diff --git a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/UnsupportedUserInteraction.kt b/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/UnsupportedUserInteraction.kt deleted file mode 100644 index fcc6299..0000000 --- a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/UnsupportedUserInteraction.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.jcaique.dialetus.utils.dataflow - -object UnsupportedUserInteraction : Throwable( - "Target interaction is not mapped" -) diff --git a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/UserInteraction.kt b/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/UserInteraction.kt deleted file mode 100644 index ce9c30c..0000000 --- a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/UserInteraction.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.jcaique.dialetus.utils.dataflow - -interface UserInteraction { - object OpenedScreen : UserInteraction - object RequestedFreshContent : UserInteraction -} diff --git a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/ViewCommand.kt b/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/ViewCommand.kt deleted file mode 100644 index 3777097..0000000 --- a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/ViewCommand.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.jcaique.dialetus.utils.dataflow - -abstract class ViewCommand diff --git a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/ViewState.kt b/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/ViewState.kt deleted file mode 100644 index 97903de..0000000 --- a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/ViewState.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.jcaique.dialetus.utils.dataflow - -sealed class ViewState { - - object FirstLaunch : ViewState() - - sealed class Loading : ViewState() { - object FromEmpty : Loading() - data class FromPrevious(val previous: T) : Loading() - } - - data class Success(val value: T) : ViewState() - data class Failed(val reason: Throwable) : ViewState() -} diff --git a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/statesPropagation.kt b/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/statesPropagation.kt deleted file mode 100644 index 3ab86d3..0000000 --- a/utils/src/main/java/com/jcaique/dialetus/utils/dataflow/statesPropagation.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.jcaique.dialetus.utils.dataflow - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow - -interface ViewStatesEmitter { - - fun observableStates(): Flow> - - val emissionScope: CoroutineScope -} - -interface ViewStateRegistry { - - fun current(): ViewState - - suspend fun store(state: ViewState) -} diff --git a/utils/src/main/java/com/jcaique/dialetus/utils/extensions/di_ktx.kt b/utils/src/main/java/com/jcaique/dialetus/utils/extensions/di_ktx.kt index c558d82..17ecd26 100644 --- a/utils/src/main/java/com/jcaique/dialetus/utils/extensions/di_ktx.kt +++ b/utils/src/main/java/com/jcaique/dialetus/utils/extensions/di_ktx.kt @@ -3,15 +3,9 @@ package com.jcaique.dialetus.utils.extensions import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.FragmentActivity import com.jcaique.dialetus.utils.KodeinTags.hostActivity -import com.jcaique.dialetus.utils.dataflow.ConfigChangesAwareStateContainer -import com.jcaique.dialetus.utils.dataflow.StateContainer -import com.jcaique.dialetus.utils.dataflow.StateMachine -import com.jcaique.dialetus.utils.dataflow.TaskExecutor import org.kodein.di.Kodein import org.kodein.di.KodeinAware -import org.kodein.di.bindings.NoArgBindingKodein import org.kodein.di.generic.bind -import org.kodein.di.generic.instance import org.kodein.di.generic.provider fun AppCompatActivity.selfInject(bindings: Kodein.MainBuilder.() -> Unit = {}) = Kodein.lazy { @@ -26,17 +20,3 @@ fun AppCompatActivity.selfInject(bindings: Kodein.MainBuilder.() -> Unit = {}) = bindings.invoke(this) } - -fun NoArgBindingKodein<*>.newStateContainer(tag: String) = - ConfigChangesAwareStateContainer( - host = instance(tag) - ) - -fun NoArgBindingKodein<*>.newStateMachine(stateContainer: StateContainer) = - StateMachine( - container = stateContainer, - executor = TaskExecutor.Concurrent( - scope = stateContainer.emissionScope, - dispatcher = instance() - ) - )