대이동: Rxjava에서 협동 프로그램으로

A detailed guide to refactoring code from rxJava to coroutines.
This post assumes you are familiar with rxJava, MVVM architecture and have a basic knowledge of coroutines.


rxJava를 광범위하게 사용하는 프로젝트가 있다고 가정하십시오.왜 안 하냐고!이것은 비동기성과 이벤트 기반의 논리를 처리하는 좋은 방법이다.많은 조작자들이 선택할 수 있고, 라인이 더욱 간단하고 추상적으로 변하며, 응용 프로그램의 각 층에서 오류를 처리하는 데 많은 노력이 필요하지 않다.덥고 차가운 관찰, 지연, 간격, 넘침 전략 등이 있는데, 그것들은 우리가 복잡한 용례를 해결하는 데 도움을 준다.만약 2년 전에 rxJava를 도입하지 않았다면, 나는 안드로이드에서 기업급 응용 프로그램을 개발하기 시작하지 않았을 것이다!
내가 여기 있는 목표는 네가 합작으로 전향하도록 설득하는 것이 아니다.프로젝트에서 사용하기를 기대하고 rxJava로 작성된 코드 라이브러리와 관련된 부분을 협동 프로그램으로 재구성하는 것을 고려했다고 가정합니다. 이것은kotlin1.3에서 안정적으로 도입된 것입니다.
협동 루트는 일종의 병행 설계 모델로 비동기적이고 비저항 코드를 작성하는 것을 더욱 쉽게 한다.
  • 무게가 가볍고 성능이 좋아요.
  • 상호 통신이 가능합니다.
  • 구조화된 동시 사용 가능
  • 지원 취소.
  • jetpack 시리즈의 다른 멤버들과 틈새 없이 통합됩니다.
  • 주요 도서관에서 지원한다.
  • 우리는 협동 프로그램 호출을 실현하기 위해 rxJava 호출을 재구성할 것입니다.저희 코드가 지루하지 않고 표현력이 더 좋아진 것도 보실 수 있습니다.먼저 rxJava 클래스를 보여 드리겠습니다. 그 다음에 협동 프로그램의 등가물을 보여 드리겠습니다.
    이 프로젝트는 다음과 같은 체계 구조를 사용한다

    Dell의 비즈니스 논리는 다음과 같습니다.
  • API에서 데이터를 가져옵니다.
  • 최신 데이터를 파일 시스템에 캐시합니다.
  • 최신 데이터를 반환합니다.
  • 네트워크가 없으면 캐시 데이터(사용 가능한 경우)를 반환합니다.
  • 사용 가능한 캐시 데이터가 없으면 - 오류가 표시됩니다.
  • 코드 연습:
    viewModel의 한 부분을 살펴보겠습니다. rxJava와liveData를 사용하고usecase 클래스를 요청한 후에 데이터를 관찰한 다음 적절한 상태 알림 UI로 나타냅니다.
        override fun getListOfCountries() {
            compositeDisposable.add(
                countryListUseCase.subscribeForData()
                    .doOnSubscribe {
                        countryListViewStates.postValue(ViewStates.CountryListStates.ShowLoading)
                    }
                    .subscribeOn(schedulerProvider.io())
                    .subscribe({ countryListDataWrapper ->
                        when (countryListDataWrapper) {
                            is UseCaseWrapper.Success -> {
                                // create a state when data is available to display
                                val currentState = ViewStates.CountryListStates.ShowContent(
                                    isLoading = false,
                                    hasError = false,
                                    errorMessage = "",
                                    showList = true,
                                    countryList = countryListDataWrapper.data.list
                                )
                                //post the current state to liveData
                                countryListViewStates.postValue(currentState)
                            }
                            is UseCaseWrapper.Failed -> {
                                //create an error state when no data is available to display
                                val currentState = ViewStates.CountryListStates.ShowContent(
                                    isLoading = false,
                                    hasError = true,
                                    errorMessage = NO_NETWORK,
                                    showList = false,
                                    countryList = ArrayList()
                                )
                                //post the current state to liveData
                                countryListViewStates.postValue(currentState)
                            }
                        }
                    }, {
                        //create a state for handling generic errors
                        val currentState = ViewStates.CountryListStates.ShowContent(
                            isLoading = false,
                            hasError = true,
                            errorMessage = SOMETHING_WENT_WRONG,
                            showList = false,
                            countryList = ArrayList()
                        )
                        //post the current state to liveData
                        countryListViewStates.postValue(currentState)
                    })
            )
        }
    
    
    
    이제 시너지 라우팅을 사용하는 동일한 논리의 코드 세그먼트를 살펴보겠습니다.
        override fun getListOfCountries() {
            viewModelScope.launch {
                when (val useCaseData = countryListUseCase.requestForData()) {
                    is UseCaseWrapper.Success -> {
                        //retrieve data received for success
                        val listOfCountries = useCaseData.data.list
                        val currentViewState = CountryListStates.ShowContent(
                            isLoading = false,
                            hasError = false,
                            errorMessage = "",
                            showList = true,
                            countryList = listOfCountries
                        )
                        //setting this view state as the current state of the stateFlow
                        countryListViewStates.value = currentViewState
                    }
                    is UseCaseWrapper.Failed -> {
                        //creating  a state for failed events
                        val currentViewState = CountryListStates.ShowContent(
                            isLoading = false,
                            hasError = true,
                            errorMessage = useCaseData.reason.value,
                            showList = false,
                            countryList = ArrayList()
                        )
                        //setting this view state as the current state of the stateFlow
                        countryListViewStates.value = currentViewState
                    }
                }
            }
        }
    
    우리는viewModel에서 데이터를 관찰하는 부분에 변화가 거의 없다.

  • usecase 클래스의 rxJava의 onError 블록을 처리했습니다.
  • 관측 가능한 rx를 관찰하는 것에 비해 일시 정지 함수의 호출 방식이 얼마나 다른가.
  • 에 또 하나의 변화가 있다. 우리는 마지막에 토론한다.
  • 이제 usecase 클래스를 살펴보겠습니다. 복잡한 작업을 완성했고viewModel 코드를 더욱 읽을 수 있고 깨끗하게 합니다.
    다음은 usecase 클래스 rxJava 버전의 Getter 방법입니다. 우리는 논리를 작성하여 데이터를 얻고publishSubject를 통해 데이터를 보냅니다.
        override fun subscribeForData(): Observable<UseCaseWrapper<DataWrapper>> {
            compositeDisposable.add(
                countryApiRepo.getData()
                    // don't block the main thread    
                    .subscribeOn(schedulerProvider.computation())
                    // store API response to local storage
                    .flatMap { return@flatMap syncDataToLocal(it) }
                    // convert network layer models to domain models for UI consumption    
                    .compose(transformCountryObjects())
                    // put data inside a wrapper for viewModel usage
                    .map { return@map mapToDataWrapper(Source.NETWORK, it) }
                    // get data from local storage in case api call throws error    
                    .onErrorResumeNext(getDataFromLocal())
                    .subscribe({ wrappedData ->
                        // There is no internet and cached data is unavailable
                        if (wrappedData.source == Source.LOCAL && wrappedData.list.isEmpty()) {
                            dataRepo.onNext(UseCaseWrapper.Failed(ReasonToFail.NO_NETWORK_AVAILABLE))
                        } else {
                            // Data is available, either from cached or from network
                            dataRepo.onNext(UseCaseWrapper.Success(wrappedData))
                        }
                    }, {
                        // Handle generic exceptions
                        dataRepo.onNext(UseCaseWrapper.Failed(ReasonToFail.SOMETHING_WENT_WRONG))
                    })
            )
            return dataRepo
        }
    
    용례류는 응용 프로그램의 업무 논리를 봉인했다.위의 rxJava 구현에서subscribeForData () 함수를 수동 함수로 만듭니다.만약 당신이 문법을 한 번 본다면, 솔직히 말하면, rxJava에 익숙하면, 그것은 매우 간결하고 이해하기 쉽다.
    하지만 당신이 1분 동안 생각해 보면, 우리는 정말 반응적으로 용례를 해결해야 합니까?캐시된 API 응답을 네트워크가 없을 때 되돌려줍니다. 이것은 우리가 시도하고 있는 것입니다.만약 네가 나에게 물었다면, 나의 졸견으로 볼 때, 우리가 코드를 수동적으로 만든 것은 단지 rxJava를 사용했기 때문이다.따라서 우리가 사용하는 도구는 우리의 범례를 정의하는 것이다.
    시너지 버전을 살펴보겠습니다.
        override suspend fun requestForData(): UseCaseWrapper<DataWrapper> {
            return try {
                //get data from either network of Local
                val (dataResponse, dataSource) = getDataFromAvailableSource()
                if (dataSource == Source.NETWORK) {
                    //sync to file storage if response is from network
                    syncDataToLocal(dataResponse)
                }
                //convert network layer model to domain layer model for UI consumption
                val mappedApiResponse = mapRawDataToModelClass(dataResponse)
                val dataWrapper = DataWrapper(mappedApiResponse, dataSource)
                //return data successfully on completion
                UseCaseWrapper.Success(dataWrapper)
            } catch (thrownExceptions: UseCaseException) {
                /*
                handle exception caused by any of the local
                functions and relay the same to UI layer
                 */
                UseCaseWrapper.Failed(thrownExceptions.reason)
            } catch (genericExceptions: Exception) {
                // generic exception handling
                UseCaseWrapper.Failed(ReasonToFail.SOMETHING_WENT_WRONG)
            }
        }
    
    coroutines 용례 클래스를 보십시오. 위에서 공유한 rxjava 클래스에 비해 현저한 변화를 볼 수 있습니다.
    협동 프로그램 구현 중, 우리는 모든 단독 논리 코드 블록에 단독 마운트 함수를 만들었다.이 함수들 중 하나는 withContext () 블록에서 협동 프로그램 역할 영역을 사용해서 실행됩니다. 이것은 우리가 메인 라인 밖에서 직렬 방식으로 이 작업을 실행하는 데 도움이 됩니다.이것은 라인을 간소화하고 반응성을 강제로 집행하지 않는다.예를 들어, 다음 함수는 API 응답을 파일 저장소에 기록하고 다른 함수도 포함합니다.
        private suspend fun syncDataToLocal(networkResponse: CountryListResponse) {
            withContext(Dispatchers.Default) {
                val content = gson.toJson(networkResponse)
                fileRepository.saveDataToLocal(content)
            }
        }
    
    이제 사용자 인터페이스의 변경 사항을 어떻게 알려주는지 다른 중요한 부분을 살펴보자.rxJava 코드에서, 우리는 liveData를 사용하여viewModel과view 사이를 통신하고, 보기에서 liveData를 관찰합니다. (이 예는 세션입니다.)
        private fun observeViewSates() {
            sharedViewModel.observeViewStates().observe(viewLifecycleOwner, Observer { viewStates ->
                when (viewStates) {
                    is ViewStates.CountryListStates.ShowLoading -> {
                        //show loader
                    }
                    is ViewStates.CountryListStates.ShowContent -> {
                        //render content state
                    }
                }
            })
        }
    
    다음은 협동 프로그램을 사용하는 대응 항목이다.
        private fun observeViewSates() {
            uiStateJob = lifecycleScope.launchWhenStarted {
                sharedViewModel.observeViewStates().collect { viewStates ->
                    when (viewStates) {
                        is CountryListStates.ShowLoading -> {
                            //show loader
                        }
                        is CountryListStates.ShowContent -> {
                            //render content state
                        }
                    }
                }
            }
        }
    
    여기서, 우리는viewModel에서 StateFlow (뜨거운 관찰 대상) 를 사용하여 UI의 상태를 유지하고, 보기에서 그것을 수집하고 있습니다.
    다음은viewModel에서 stateFlow를 초기화하는 방법입니다.
    private val countryListViewStates = MutableStateFlow<CountryListStates>(
                CountryListStates.ShowLoading
            )
    

    다음은stateflow와liveData 사이의 차이점--stateflow가liveData보다 약간의 장점이 있다

  • StateFlow 값이 업데이트되는 스레드를 걱정할 필요가 없습니다.
  • 항상 정의된 초기 상태가 있습니다.
  • 값이 비어 있다는 보증 - 당신이 관찰하는 곳에서는 당연히 비어 있는 값을 설정할 수 없기 때문이다.
  • 그러나 이것은 추가 대가를 치러야 한다.

  • StateFlow는liveData처럼 생명주기 의식을 가지고 있지 않다.
  • 상태 흐름의 생명 주기를 의식하기 위해 우리는 생명 주기 가동 루트 범위를 사용하고 있다.이렇게 하면 뷰가 더 이상 활성 상태가 아닌 경우 UI 이벤트가 표시되지 않습니다.
  • 그러나 UI가 다시 활성화되면 이전 범위에서 업데이트가 계속 수신되므로 바탕 화면에서 명시적으로 시작되는 상호 작용 라우팅 범위를 해제해야 합니다.
  • [안드로이드 공식 문서에서 더 많은 내용을 읽을 수 있습니다]: https://developer.android.com/kotlin/flow/stateflow-and-sharedflow
    따라서 라이프 사이클 감지의 라이브 데이터를 원하느냐, UI 층의 State Flow를 원하느냐는 논리를 어떻게 처리하느냐에 달려 있다.
    rxJava는 좋은 학습 곡선을 가지고 있다.일단 네가 익숙해지면, 그것은 의심할 여지없이 생명을 구하는 볏짚이다.그러나 우리가 이렇게 절박하게 반응할 필요가 없는 부분에서 우리는 협동 프로그램으로 쉽게 전환하고 효과적으로 일을 완성할 수 있다.
    다음은github에 있는 항목의 링크입니다.주 분기에는 rxJava 코드가 있고 분기 분기 coroutine에는 coroutine 마이그레이션 코드가 있습니다.

    무크제예비치 / 국가 명단



    국가 명단


    오픈 API에서 국가 목록을 가져와 회수기 보기에 표시하는 안드로이드 응용 프로그램


    응용 프로그램은 MVVM 구조를 사용하는데, 아래는 structire이다

    개방형 API에서 국가 목록을 가져와 파일 시스템에 캐시했습니다.
    네트워크가 없을 때, 우리는 캐시된 데이터를 되돌려줍니다
    캐시에 사용할 수 있는 데이터가 없으면 사용자에게 오류를 표시합니다.
    이 앱은 RXJava를 사용해 다거 안드로이드와 함께 기술 스택으로 개조했다.
    View on GitHub
    표지 사진 작성자Mariko margetsonUnsplash

    좋은 웹페이지 즐겨찾기