From 39d080fdaa10a6237f64d6ac271230c1f5e1ebd3 Mon Sep 17 00:00:00 2001 From: FunkyMuse Date: Mon, 2 May 2022 23:39:55 +0200 Subject: [PATCH] BREAKING CHANGE: ViewState has a ViewStatefulEvent.kt as a state descriptor and payload as something that holds the value ViewStateContract.kt data is now renamed to viewState ViewEventContract now doesn't force you to integrate val viewEvent: Flow and is also a functional contract Check MVIFragment.kt for sample --- .../setofusefulkotlinextensions/TestAVM.kt | 11 +-- .../ViewEventProvider.kt | 10 +-- .../nav/MVIFragment.kt | 30 +++---- .../retrofit/viewstate/event/ViewEvent.kt | 63 -------------- .../viewstate/event/ViewEventContract.kt | 9 +- .../viewstate/event/ViewStatefulEvent.kt | 85 +++++++++++++++++++ .../retrofit/viewstate/state/ViewState.kt | 15 ++-- .../viewstate/state/ViewStateContract.kt | 3 +- .../viewstate/state/ViewStateExtensions.kt | 50 +++++++---- .../com/crazylegend/retrofit/FakeViewEvent.kt | 16 ++++ .../com/crazylegend/retrofit/ViewStateTest.kt | 27 +++--- 11 files changed, 181 insertions(+), 138 deletions(-) delete mode 100644 retrofit/src/main/java/com/crazylegend/retrofit/viewstate/event/ViewEvent.kt create mode 100644 retrofit/src/main/java/com/crazylegend/retrofit/viewstate/event/ViewStatefulEvent.kt create mode 100644 retrofit/src/test/java/com/crazylegend/retrofit/FakeViewEvent.kt diff --git a/app/src/main/java/com/crazylegend/setofusefulkotlinextensions/TestAVM.kt b/app/src/main/java/com/crazylegend/setofusefulkotlinextensions/TestAVM.kt index 49e94c152..2c3aed93a 100644 --- a/app/src/main/java/com/crazylegend/setofusefulkotlinextensions/TestAVM.kt +++ b/app/src/main/java/com/crazylegend/setofusefulkotlinextensions/TestAVM.kt @@ -10,8 +10,8 @@ import com.crazylegend.retrofit.adapter.ApiResultAdapterFactory import com.crazylegend.retrofit.apiresult.ApiResult import com.crazylegend.retrofit.interceptors.ConnectivityInterceptor import com.crazylegend.retrofit.randomPhotoIndex +import com.crazylegend.retrofit.viewstate.event.ViewStatefulEvent import com.crazylegend.retrofit.viewstate.state.ViewState -import com.crazylegend.retrofit.viewstate.state.ViewStateContract import com.crazylegend.retrofit.viewstate.state.asViewStatePayloadWithEvents import com.crazylegend.setofusefulkotlinextensions.adapter.TestModel import kotlinx.coroutines.delay @@ -33,13 +33,10 @@ class TestAVM( ) : AndroidViewModel(application) { private val viewEventProvider = ViewEventProvider() - private val viewState = ViewState>(viewEventProvider) + val viewState = ViewState>(viewEventProvider) //use delegation to avoid having these properties since the view event provider can be injected easily - val payload: List? get() = viewState.payload - val isDataNotLoaded get() = viewState.isDataNotLoaded - val isDataLoaded: Boolean get() = viewState.isDataLoaded - val viewEvent = viewEventProvider.viewEvent + val viewEvent = viewEventProvider.viewStatefulEvent // sealed class TestAVMIntent { @@ -52,7 +49,7 @@ class TestAVM( } private val intents = MutableSharedFlow() - val posts: StateFlow>> = viewState.data + val posts: StateFlow = viewState.viewState private fun getPosts() { viewModelScope.launch { diff --git a/app/src/main/java/com/crazylegend/setofusefulkotlinextensions/ViewEventProvider.kt b/app/src/main/java/com/crazylegend/setofusefulkotlinextensions/ViewEventProvider.kt index 4e7964d85..94e697bca 100644 --- a/app/src/main/java/com/crazylegend/setofusefulkotlinextensions/ViewEventProvider.kt +++ b/app/src/main/java/com/crazylegend/setofusefulkotlinextensions/ViewEventProvider.kt @@ -1,6 +1,6 @@ package com.crazylegend.setofusefulkotlinextensions -import com.crazylegend.retrofit.viewstate.event.ViewEvent +import com.crazylegend.retrofit.viewstate.event.ViewStatefulEvent import com.crazylegend.retrofit.viewstate.event.ViewEventContract import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel @@ -11,10 +11,10 @@ import kotlinx.coroutines.withContext //should be injected class ViewEventProvider : ViewEventContract { - private val channelEvents: Channel = Channel(Channel.BUFFERED) - override val viewEvent: Flow = channelEvents.receiveAsFlow() + private val channelEvents: Channel = Channel(Channel.BUFFERED) + val viewStatefulEvent: Flow = channelEvents.receiveAsFlow() - override suspend fun provideEvent(viewEvent: ViewEvent) = withContext(Dispatchers.Main.immediate) { - channelEvents.send(viewEvent) + override suspend fun provideEvent(viewStatefulEvent: ViewStatefulEvent) = withContext(Dispatchers.Main.immediate) { + channelEvents.send(viewStatefulEvent) } } \ No newline at end of file diff --git a/app/src/main/java/com/crazylegend/setofusefulkotlinextensions/nav/MVIFragment.kt b/app/src/main/java/com/crazylegend/setofusefulkotlinextensions/nav/MVIFragment.kt index 45eea00bf..ea1ee5aff 100644 --- a/app/src/main/java/com/crazylegend/setofusefulkotlinextensions/nav/MVIFragment.kt +++ b/app/src/main/java/com/crazylegend/setofusefulkotlinextensions/nav/MVIFragment.kt @@ -11,17 +11,18 @@ import com.crazylegend.common.ifTrue import com.crazylegend.internetdetector.InternetDetector import com.crazylegend.lifecycle.repeatingJobOnStarted import com.crazylegend.lifecycle.viewCoroutineScope -import com.crazylegend.retrofit.apiresult.* import com.crazylegend.retrofit.throwables.isNoConnectionException -import com.crazylegend.retrofit.viewstate.* -import com.crazylegend.retrofit.viewstate.event.ViewEvent +import com.crazylegend.retrofit.viewstate.event.ViewStatefulEvent import com.crazylegend.retrofit.viewstate.event.asApiErrorBody +import com.crazylegend.retrofit.viewstate.event.getAsThrowable import com.crazylegend.retrofit.viewstate.event.isApiError import com.crazylegend.retrofit.viewstate.event.isError import com.crazylegend.retrofit.viewstate.state.handleApiError +import com.crazylegend.retrofit.viewstate.state.showEmptyDataOnErrors +import com.crazylegend.retrofit.viewstate.state.showLoadingWhenDataIsLoaded +import com.crazylegend.retrofit.viewstate.state.showLoadingWhenDataNotLoaded import com.crazylegend.setofusefulkotlinextensions.R import com.crazylegend.setofusefulkotlinextensions.TestAVM -import com.crazylegend.setofusefulkotlinextensions.adapter.TestModel import com.crazylegend.setofusefulkotlinextensions.adapter.TestViewBindingAdapter import com.crazylegend.setofusefulkotlinextensions.databinding.FragmentTestBinding import com.crazylegend.view.setIsNotRefreshing @@ -55,17 +56,18 @@ class MVIFragment : Fragment(R.layout.fragment_test) { binding.swipeToRefresh.setOnRefreshListener { binding.swipeToRefresh.setIsRefreshing() getApiPosts() + binding.swipeToRefresh.setIsNotRefreshing() } binding.staticPosts.setOnClickListenerCooldown { testAVM.sendEvent(TestAVM.TestAVMIntent.GetRandomPosts) } } - private fun handleViewEvents(viewEvent: ViewEvent) { - viewEvent.isError.and(testAVM.isDataLoaded).ifTrue(::showErrorSnack) - viewEvent.isApiError.ifTrue { + private fun handleViewEvents(viewStatefulEvent: ViewStatefulEvent) { + viewStatefulEvent.isError.and(testAVM.viewState.isDataLoaded).ifTrue(::showErrorSnack) + viewStatefulEvent.isApiError.ifTrue { toast?.cancel() - toast = Toast.makeText(requireContext(), handleApiError(testAVM.savedStateHandle, viewEvent.asApiErrorBody), LENGTH_LONG) + toast = Toast.makeText(requireContext(), handleApiError(testAVM.savedStateHandle, viewStatefulEvent.asApiErrorBody), LENGTH_LONG) toast?.show() } } @@ -74,14 +76,12 @@ class MVIFragment : Fragment(R.layout.fragment_test) { testAVM.sendEvent(TestAVM.TestAVMIntent.GetPosts) } - private fun updateUIState(apiResult: ApiResult>) { + private fun updateUIState(apiResult: ViewStatefulEvent) { apiResult.getAsThrowable?.isNoConnectionException?.ifTrue(::retryOnInternetAvailable) - - apiResult.isNotLoading.ifTrue { binding.swipeToRefresh.setIsNotRefreshing() } - binding.error.isVisible = testAVM.isDataNotLoaded and (apiResult.isError || apiResult.isApiError) - binding.centerBigLoading.isVisible = apiResult.isLoading and testAVM.isDataNotLoaded - binding.progress.isVisible = apiResult.isLoading and testAVM.isDataLoaded - adapter.submitList(testAVM.payload) + binding.error.isVisible = testAVM.viewState.showEmptyDataOnErrors + binding.centerBigLoading.isVisible = testAVM.viewState.showLoadingWhenDataNotLoaded + binding.progress.isVisible = testAVM.viewState.showLoadingWhenDataIsLoaded + adapter.submitList(testAVM.viewState.payload) } private fun showErrorSnack() { diff --git a/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/event/ViewEvent.kt b/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/event/ViewEvent.kt deleted file mode 100644 index becdbcf40..000000000 --- a/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/event/ViewEvent.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.crazylegend.retrofit.viewstate.event - -import okhttp3.ResponseBody - -/** - * Created by funkymuse on 11/20/21 to long live and prosper ! - */ -sealed interface ViewEvent { - data class Error(val throwable: Throwable) : ViewEvent - data class ApiError(val errorBody: ResponseBody?, val code: Int) : ViewEvent - - object Success : ViewEvent - object Loading : ViewEvent - object Idle : ViewEvent -} - -val ViewEvent.isLoading get() = this is ViewEvent.Loading -val ViewEvent.isIdle get() = this is ViewEvent.Idle -val ViewEvent.isError get() = this is ViewEvent.Error -val ViewEvent.isApiError get() = this is ViewEvent.ApiError -val ViewEvent.isSuccess get() = this is ViewEvent.Success - -val ViewEvent.asError get() = (this as ViewEvent.Error) -val ViewEvent.asErrorThrowable get() = (this as ViewEvent.Error).throwable -val ViewEvent.asApiError get() = (this as ViewEvent.ApiError) -val ViewEvent.asApiErrorBody get() = (this as ViewEvent.ApiError).errorBody -val ViewEvent.asApiErrorCode get() = (this as ViewEvent.ApiError).code - -fun ViewEvent.onError(action: (Throwable) -> Unit): ViewEvent { - if (this is ViewEvent.Error) { - action(throwable) - } - return this -} - -fun ViewEvent.onSuccess(action: () -> Unit): ViewEvent { - if (this is ViewEvent.Success) { - action() - } - return this -} - -fun ViewEvent.onIdle(action: () -> Unit): ViewEvent { - if (this is ViewEvent.Idle) { - action() - } - return this -} - -fun ViewEvent.onLoading(action: () -> Unit): ViewEvent { - if (this is ViewEvent.Loading) { - action() - } - return this -} - -fun ViewEvent.onApiError(action: (errorBody: ResponseBody?, responseCode: Int) -> Unit): ViewEvent { - if (this is ViewEvent.ApiError) { - action(errorBody, code) - } - return this -} - diff --git a/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/event/ViewEventContract.kt b/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/event/ViewEventContract.kt index d7014f717..7e057ab52 100644 --- a/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/event/ViewEventContract.kt +++ b/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/event/ViewEventContract.kt @@ -1,10 +1,5 @@ package com.crazylegend.retrofit.viewstate.event -import kotlinx.coroutines.flow.Flow - -interface ViewEventContract { - //decouple this as well so that a direct flow is not forced, hmm - val viewEvent: Flow - - suspend fun provideEvent(viewEvent: ViewEvent) +fun interface ViewEventContract { + suspend fun provideEvent(viewStatefulEvent: ViewStatefulEvent) } \ No newline at end of file diff --git a/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/event/ViewStatefulEvent.kt b/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/event/ViewStatefulEvent.kt new file mode 100644 index 000000000..44692d9be --- /dev/null +++ b/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/event/ViewStatefulEvent.kt @@ -0,0 +1,85 @@ +package com.crazylegend.retrofit.viewstate.event + +import okhttp3.ResponseBody + +/** + * Created by funkymuse on 11/20/21 to long live and prosper ! + */ +sealed interface ViewStatefulEvent { + data class Error(val throwable: Throwable) : ViewStatefulEvent + data class ApiError(val errorBody: ResponseBody?, val responseCode: Int) : ViewStatefulEvent + + object Success : ViewStatefulEvent + object Loading : ViewStatefulEvent + object Idle : ViewStatefulEvent +} + +val ViewStatefulEvent.isLoading get() = this is ViewStatefulEvent.Loading +val ViewStatefulEvent.isNotLoading get() = this !is ViewStatefulEvent.Loading + +val ViewStatefulEvent.isIdle get() = this is ViewStatefulEvent.Idle +val ViewStatefulEvent.isNotIdle get() = this !is ViewStatefulEvent.Idle + +val ViewStatefulEvent.isError get() = this is ViewStatefulEvent.Error +val ViewStatefulEvent.isNotError get() = this !is ViewStatefulEvent.Error + +val ViewStatefulEvent.isApiError get() = this is ViewStatefulEvent.ApiError +val ViewStatefulEvent.isNotApiError get() = this !is ViewStatefulEvent.ApiError + +val ViewStatefulEvent.isErrorOrApiError get() = this is ViewStatefulEvent.Error || this is ViewStatefulEvent.ApiError + +val ViewStatefulEvent.isSuccess get() = this is ViewStatefulEvent.Success +val ViewStatefulEvent.isNotSuccess get() = this !is ViewStatefulEvent.Success + +val ViewStatefulEvent.asError get() = (this as ViewStatefulEvent.Error) +val ViewStatefulEvent.asErrorThrowable get() = (this as ViewStatefulEvent.Error).throwable +val ViewStatefulEvent.asApiError get() = (this as ViewStatefulEvent.ApiError) +val ViewStatefulEvent.asApiErrorBody get() = (this as ViewStatefulEvent.ApiError).errorBody +val ViewStatefulEvent.asApiErrorCode get() = (this as ViewStatefulEvent.ApiError).responseCode + + +val ViewStatefulEvent.getAsThrowable: Throwable? get() = if (this is ViewStatefulEvent.Error) throwable else null +val ViewStatefulEvent.getAsApiFailureCode: Int? get() = if (this is ViewStatefulEvent.ApiError) responseCode else null +val ViewStatefulEvent.getAsApiResponseBody: ResponseBody? get() = if (this is ViewStatefulEvent.ApiError) errorBody else null + +fun ViewStatefulEvent.asSuccess() = this as ViewStatefulEvent.Success +fun ViewStatefulEvent.asLoading() = this as ViewStatefulEvent.Loading +fun ViewStatefulEvent.asError() = this as ViewStatefulEvent.Error +fun ViewStatefulEvent.asApiError() = this as ViewStatefulEvent.ApiError +fun ViewStatefulEvent.asIdle() = this as ViewStatefulEvent.Idle + +inline fun ViewStatefulEvent.onError(action: (Throwable) -> Unit): ViewStatefulEvent { + if (this is ViewStatefulEvent.Error) { + action(throwable) + } + return this +} + +inline fun ViewStatefulEvent.onSuccess(action: () -> Unit): ViewStatefulEvent { + if (this is ViewStatefulEvent.Success) { + action() + } + return this +} + +inline fun ViewStatefulEvent.onIdle(action: () -> Unit): ViewStatefulEvent { + if (this is ViewStatefulEvent.Idle) { + action() + } + return this +} + +inline fun ViewStatefulEvent.onLoading(action: () -> Unit): ViewStatefulEvent { + if (this is ViewStatefulEvent.Loading) { + action() + } + return this +} + +inline fun ViewStatefulEvent.onApiError(action: (errorBody: ResponseBody?, responseCode: Int) -> Unit): ViewStatefulEvent { + if (this is ViewStatefulEvent.ApiError) { + action(errorBody, responseCode) + } + return this +} + diff --git a/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/state/ViewState.kt b/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/state/ViewState.kt index 9c871920f..27bc538f9 100644 --- a/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/state/ViewState.kt +++ b/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/state/ViewState.kt @@ -1,12 +1,7 @@ package com.crazylegend.retrofit.viewstate.state import com.crazylegend.retrofit.apiresult.ApiResult -import com.crazylegend.retrofit.apiresult.onApiErrorSuspend -import com.crazylegend.retrofit.apiresult.onErrorSuspend -import com.crazylegend.retrofit.apiresult.onIdleSuspend -import com.crazylegend.retrofit.apiresult.onLoadingSuspend -import com.crazylegend.retrofit.apiresult.onSuccessSuspend -import com.crazylegend.retrofit.viewstate.event.ViewEvent +import com.crazylegend.retrofit.viewstate.event.ViewStatefulEvent import com.crazylegend.retrofit.viewstate.event.ViewEventContract import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -16,11 +11,11 @@ import kotlinx.coroutines.flow.asStateFlow */ class ViewState( private val viewEventContract: ViewEventContract? = null, - defaultApiState: ApiResult = ApiResult.Idle + defaultViewState: ViewStatefulEvent = ViewStatefulEvent.Idle ) : ViewStateContract { - private val dataState: MutableStateFlow> = MutableStateFlow(defaultApiState) - override val data = dataState.asStateFlow() + private val _viewState: MutableStateFlow = MutableStateFlow(defaultViewState) + override val viewState = _viewState.asStateFlow() override var payload: T? = null @@ -29,7 +24,7 @@ class ViewState( } override fun emitState(apiResult: ApiResult) { - dataState.value = apiResult + _viewState.value = apiResult.asViewEvent() } override val isDataLoaded get() = payload != null diff --git a/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/state/ViewStateContract.kt b/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/state/ViewStateContract.kt index ca50d209d..aac76b3ee 100644 --- a/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/state/ViewStateContract.kt +++ b/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/state/ViewStateContract.kt @@ -1,13 +1,14 @@ package com.crazylegend.retrofit.viewstate.state import com.crazylegend.retrofit.apiresult.ApiResult +import com.crazylegend.retrofit.viewstate.event.ViewStatefulEvent import kotlinx.coroutines.flow.StateFlow /** * Created by funkymuse on 11/20/21 to long live and prosper ! */ interface ViewStateContract { - val data : StateFlow> + val viewState : StateFlow var payload: T? diff --git a/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/state/ViewStateExtensions.kt b/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/state/ViewStateExtensions.kt index ad79cdb29..1125aeed2 100644 --- a/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/state/ViewStateExtensions.kt +++ b/retrofit/src/main/java/com/crazylegend/retrofit/viewstate/state/ViewStateExtensions.kt @@ -1,16 +1,20 @@ package com.crazylegend.retrofit.viewstate.state import androidx.lifecycle.SavedStateHandle -import com.crazylegend.retrofit.apiresult.* +import com.crazylegend.retrofit.apiresult.ApiResult +import com.crazylegend.retrofit.apiresult.onSuccess import com.crazylegend.retrofit.throwables.isNoConnectionException -import com.crazylegend.retrofit.viewstate.event.ViewEvent +import com.crazylegend.retrofit.viewstate.event.ViewStatefulEvent +import com.crazylegend.retrofit.viewstate.event.asError +import com.crazylegend.retrofit.viewstate.event.isApiError +import com.crazylegend.retrofit.viewstate.event.isError +import com.crazylegend.retrofit.viewstate.event.isLoading +import com.crazylegend.retrofit.viewstate.event.isSuccess import okhttp3.ResponseBody /** * Created by funkymuse on 11/20/21 to long live and prosper ! */ - - fun ApiResult.asViewStatePayload(viewState: ViewStateContract): ApiResult { onSuccess { viewState.payload = it @@ -48,7 +52,7 @@ suspend fun ViewStateContract.fromRetrofitWithEvents(apiResult: ApiResult } -private const val errorStateKey = "errorJSONKeyApiResult" +private const val errorStateKey = "viewstate.state.errorStateKey.errorJSONKeyApiResult" fun SavedStateHandle.handleApiErrorFromSavedState(errorBody: ResponseBody?): String? { val json = errorBody?.string() @@ -64,48 +68,60 @@ fun handleApiError(savedStateHandle: SavedStateHandle, errorBody: ResponseBody?) val ViewStateContract.showEmptyDataOnErrorsOrSuccess: Boolean get() { - val retrofitResult = data.value + val retrofitResult = viewState.value return isDataNotLoaded && (retrofitResult.isError or retrofitResult.isApiError or retrofitResult.isSuccess) } val ViewStateContract.showEmptyDataOnErrors: Boolean get() { - val retrofitResult = data.value + val retrofitResult = viewState.value return isDataNotLoaded && (retrofitResult.isError or retrofitResult.isApiError) } val ViewStateContract.showEmptyDataOnApiError: Boolean get() { - val retrofitResult = data.value + val retrofitResult = viewState.value return isDataNotLoaded && (retrofitResult.isApiError) } val ViewStateContract.showEmptyDataOnError: Boolean get() { - val retrofitResult = data.value + val retrofitResult = viewState.value return isDataNotLoaded && (retrofitResult.isError) } val ViewStateContract.showEmptyDataOnSuccess: Boolean get() { - val retrofitResult = data.value + val retrofitResult = viewState.value return isDataNotLoaded && retrofitResult.isSuccess } -val ViewStateContract.shouldShowEmptyDataOnNoConnection: Boolean +val ViewStateContract.showEmptyDataOnNoConnection: Boolean get() { - val retrofitResult = data.value + val retrofitResult = viewState.value return isDataNotLoaded && retrofitResult.isError && retrofitResult.asError().throwable.isNoConnectionException } +val ViewStateContract.showLoadingWhenDataNotLoaded: Boolean + get() { + val retrofitResult = viewState.value + return retrofitResult.isLoading and isDataNotLoaded + } + +val ViewStateContract.showLoadingWhenDataIsLoaded: Boolean + get() { + val retrofitResult = viewState.value + return retrofitResult.isLoading and isDataLoaded + } + fun ApiResult.asViewEvent() = when (this) { - is ApiResult.ApiError -> ViewEvent.ApiError(errorBody, responseCode) - is ApiResult.Error -> ViewEvent.Error(throwable) - ApiResult.Idle -> ViewEvent.Idle - ApiResult.Loading -> ViewEvent.Loading - is ApiResult.Success -> ViewEvent.Success + is ApiResult.ApiError -> ViewStatefulEvent.ApiError(errorBody, responseCode) + is ApiResult.Error -> ViewStatefulEvent.Error(throwable) + ApiResult.Idle -> ViewStatefulEvent.Idle + ApiResult.Loading -> ViewStatefulEvent.Loading + is ApiResult.Success -> ViewStatefulEvent.Success } \ No newline at end of file diff --git a/retrofit/src/test/java/com/crazylegend/retrofit/FakeViewEvent.kt b/retrofit/src/test/java/com/crazylegend/retrofit/FakeViewEvent.kt new file mode 100644 index 000000000..20a4ab755 --- /dev/null +++ b/retrofit/src/test/java/com/crazylegend/retrofit/FakeViewEvent.kt @@ -0,0 +1,16 @@ +package com.crazylegend.retrofit + +import com.crazylegend.retrofit.viewstate.event.ViewEventContract +import com.crazylegend.retrofit.viewstate.event.ViewStatefulEvent +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.receiveAsFlow + +class FakeViewEvent : ViewEventContract { + + private val state = Channel(Channel.BUFFERED) + val event = state.receiveAsFlow() + + override suspend fun provideEvent(viewStatefulEvent: ViewStatefulEvent) { + state.send(viewStatefulEvent) + } +} \ No newline at end of file diff --git a/retrofit/src/test/java/com/crazylegend/retrofit/ViewStateTest.kt b/retrofit/src/test/java/com/crazylegend/retrofit/ViewStateTest.kt index 6bdc341dd..069ca362f 100644 --- a/retrofit/src/test/java/com/crazylegend/retrofit/ViewStateTest.kt +++ b/retrofit/src/test/java/com/crazylegend/retrofit/ViewStateTest.kt @@ -3,6 +3,7 @@ package com.crazylegend.retrofit import com.crazylegend.retrofit.apiresult.* import com.crazylegend.retrofit.throwables.NoConnectionException import com.crazylegend.retrofit.throwables.isNoConnectionException +import com.crazylegend.retrofit.viewstate.event.asError import com.crazylegend.retrofit.viewstate.event.isError import com.crazylegend.retrofit.viewstate.event.isLoading import com.crazylegend.retrofit.viewstate.event.isSuccess @@ -11,10 +12,10 @@ import com.crazylegend.retrofit.viewstate.state.asViewStatePayload import com.crazylegend.retrofit.viewstate.state.asViewStatePayloadWithEvents import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue -import org.junit.Rule import org.junit.Test /** @@ -23,12 +24,11 @@ import org.junit.Test @ExperimentalCoroutinesApi class ViewStateTest { - @get:Rule - var mainCoroutineRule = MainCoroutineRule() + private val testScope = TestScope() @Test - fun `view state payload once`() = mainCoroutineRule.runBlockingTest { + fun `view state payload once`() = testScope.runTest { val retrofitResult = ApiResult.Success(listOf("")) val viewState = ViewState>() @@ -72,30 +72,31 @@ class ViewStateTest { } @Test - fun `view state payload event`() = mainCoroutineRule.runBlockingTest { + fun `view state payload event`() = testScope.runTest { val testList = listOf("") val retrofitResult = ApiResult.Success(testList) - val viewState = ViewState>() + val viewEvent = FakeViewEvent() + val viewState = ViewState>(viewEvent) retrofitResult.asViewStatePayloadWithEvents(viewState) - val firstItem = viewState.viewEvent.first() - val firstState = viewState.data.first() + val firstItem = viewEvent.event.first() + val firstState = viewState.viewState.first() assertTrue(firstState.isSuccess) assertTrue(firstItem.isSuccess) ApiResult.Loading.asViewStatePayloadWithEvents(viewState) - val firstItemSecondTime = viewState.viewEvent.first() - val firstStateSecondTime = viewState.data.first() + val firstItemSecondTime = viewEvent.event.first() + val firstStateSecondTime = viewState.viewState.first() assertTrue(firstStateSecondTime.isLoading) assertTrue(firstItemSecondTime.isLoading) ApiResult.Error(NoConnectionException()).asViewStatePayloadWithEvents(viewState) - val firstItemThirdTime = viewState.viewEvent.first() - val firstStateThirdTime = viewState.data.first() + val firstItemThirdTime = viewEvent.event.first() + val firstStateThirdTime = viewState.viewState.first() assertTrue(firstStateThirdTime.isError) assertTrue(firstItemThirdTime.isError) assertTrue(viewState.payload != null)