[Android] MVVM 패턴 Retrofit 가이드 Kotlin

: ) YOUNG·2023년 1월 21일
1

안드로이드

목록 보기
6/17
post-thumbnail

MVVM 패턴



순서

처음 Server와 통신 순서

Fragment -> ViewModel -> Repository -> Api

다시 데이터를 반환 받는 순서 (그냥 반대로 하면됨)

Api -> Repository -> ViewModel -> Fragment



Api결과를 처리할 수 있는 모듈

NetworkResult.kt


sealed class NetworkResult<T>(val data: T? = null, val message: String? = null) {

	class Success<T>(data: T) : NetworkResult<T>(data)
    class Error<T>(message: String?, data: T? = null) : NetworkResult<T>(data, message)
    class Loading<T> : NetworkResult<T>()

} // End of NetworkResult sealed class

sealed class로 성공, 로딩, 실패를 구분할 수 있는 클래스를 분리해놓고 나중에 api결과에 따라서 결과를 구분할 수 있도록 해줌


Fragment.kt


		// 버튼 눌렀을 때,
        binding.nextButton.setOnClickListener {
            // 이메일 인증 버튼을 눌렀을 때, 이메일 사용여부 체크하는 통신 적용
            CoroutineScope(Dispatchers.IO).launch {
                joinViewModel.emailUsedCheck(Email(binding.emailEd.text.toString()))
            }
        }

프래그먼트 내부에서 api를 호출하는 메소드를 실행한다.

비동기처리를 해야하므로 CoroutinScope를 사용한다.


ViewModel.kt


    // 이메일 중복 체크 통신
    fun emailUsedCheck(emailId: Email) {
        viewModelScope.launch {
            userRepository.checkUsedEmailId(emailId)
        }
    } // End of emailUsedCheck
 

ViewModel에서는 viewModelScope를 사용해서 비동기처리를 통해
Repository에 있는 메소드를 실행한다.


Repository.kt


    // 이메일 중복 체크
    suspend fun checkUsedEmailId(emailId: Email) {

        try {
            val response = userApi.isUsedEmailId(emailId)

            // 처음은 Loading 상태로 지정
            _userResponseLiveData.postValue(NetworkResult.Loading())

            if (response.isSuccessful && response.body() != null) {
                _userResponseLiveData.postValue(NetworkResult.Success(response.body()!!))
            } else if (response.errorBody() != null) {
                _userResponseLiveData.postValue(
                    NetworkResult.Error(
                        response.errorBody()!!.string()
                    )
                )
            } else {
                _userResponseLiveData.postValue(NetworkResult.Error(response.headers().toString()))
            }
        } catch (e: java.lang.Exception) {

        }

    } // End of checkEmailId

response 변수에 api결과값을 담아서 가져온다.

response를 하는 과정에서 오류가 발생할 수 있기 때문에 try catch 내부에서 response를 실행한다.

이건 어디까지나 개인선택이다. 에러 처리를 따로 하고싶지 않다면, try catch를 사용하지 않아도 상관없다.

안해도 잘 작동한다. 404 등의 문제는 else if(response.errorBody()) 에서 처리할 수 있다.


api Interface.kt


    // 사용자 이메일 사용 여부 체크
    @POST("accounts/emailcheck/")
    suspend fun isUsedEmailId(
        @Body emailId: Email
    ): Response<Boolean>

interface의 api를 받아오는 return 값은 Response에서 제너릭에 받아오는 타입을 적어주면된다.
여기서는 true, false로 넘어오니까 Boolean, 객체로 넘어오면 그냥 dto 객체를 적어주면된다.

이제 이 결과를 가지고 다시 repository로 돌아간다.


Repository.kt


    // 이메일 중복 체크
    suspend fun checkUsedEmailId(emailId: Email) {

        try {
            val response = userApi.isUsedEmailId(emailId)

            // 처음은 Loading 상태로 지정
            _userResponseLiveData.postValue(NetworkResult.Loading())

            if (response.isSuccessful && response.body() != null) {
                _userResponseLiveData.postValue(NetworkResult.Success(response.body()!!))
            } else if (response.errorBody() != null) {
                _userResponseLiveData.postValue(
                    NetworkResult.Error(
                        response.errorBody()!!.string()
                    )
                )
            } else {
                _userResponseLiveData.postValue(NetworkResult.Error(response.headers().toString()))
            }
        } catch (e: java.lang.Exception) {

        }

    } // End of checkEmailId

ViewModel.kt


    // 이메일 중복체크 & 인증번호를 관리하는 LiveData
    val emailValidateResponse: LiveData<NetworkResult<Boolean>>
        get() = userRepository.emailValidateResponseLiveData

이제 ViewModel로 간다.

viewModel에서는 Repository에서 나오는 결과값을 LiveData가 받게 된다.
즉, viewModel의 LiveData가 Repository에 있는 Api의 상태값을 받아오는 LiveData를 바라보도록 만드는 것이다.

Ex)
Fragment가 ViewModel의 LiveData를 Observe함

ViewModel의 LiveData는 Repository의 LiveData를 get()해옴 (바라본다는 개념)

Repository에서 private LiveData를 만듬, 이 LiveData가 api의 reponse값을 가져와서 LiveData에 저장하게 됨.


Fragment.kt


    private fun isUsedEmailObserver() {
        joinViewModel.userResponseLiveData.observe(viewLifecycleOwner) {
            binding.joinEmailProgressbar.isVisible = false
            binding.joinEmailProgressbar.visibility = View.GONE
            firstEditTextMessageTv.setTextColor(R.color.black)

            when (it) {
                is NetworkResult.Success -> {
                    if (it.data == true) {
                        // 사용중인 이메일
                        emailWarningTextView.setText(R.string.isused_email_text)
                        secondTextFieldLayout.visibility = View.GONE
                        firstEditTextMessageTv.setTextColor(R.color.error_color)
                    }

                    if (it.data == false) {
                        // 사용가능한 이메일
                        secondTextFieldLayout.visibility = View.VISIBLE
                        emailWarningTextView.setText(R.string.can_use_email_text)
                    }
                }
                is NetworkResult.Error -> {
                    Log.d(TAG, "이메일 체크 Error: ${it.data}")
                }
                is NetworkResult.Loading -> {
                    binding.joinEmailProgressbar.isVisible = true
                    binding.joinEmailProgressbar.visibility = View.VISIBLE
                }
            }
        }

    } // End of isUsedEmailObserver

viewModel에서 server의 response 결과값을 livedata로 처리하기 때문에 해당 결과값을 observe해서 상태값을 감지한다

그리고 위에서 만든 NetworkResult를 담고있는 결과값을 반환 받아서 해당 값을 it으로 받아서 성공일 경우에 따라 구현체를 만들면 된다.

통신을 하는 과정에서 생기는 loading 타임을 표시하기 위해 progressbar가 보일 수 있도록 처음에는 progressbar를 false로 해두고

NetworkResult.Loading에서 progressbar를 true로 해주면 loading 인 상황에서만 progressbar가 보이도록 할 수 있다.


0개의 댓글