[Android] MVVM 패턴

fanthasium·2022년 11월 18일
0

앱 아키텍처

안드로이드를 가볍게 Activity,Fragment 하나로 구성 한다면 상관이 없지만,
프로젝트 규모가 커짐에 따라 Activity 숫자는 증가하며 그에 따라 Fragment의 수도 증가하게 됩니다.
그러다보면 UI기반 class인 Activty안에서 view와의 상호작용 외, 데이터를 처리하는 로직도 써버리게 되는데 이게!! 쌓이다보면 Activity의 과부하를 야기시킵니다..

(내 일도 아닌데 자꾸 시키는건 싫잖아요?..)

(위 아키텍처 다이어그램 처럼 구조를 짜게 되면 내 일 아닌 일을 분담 시켜줄 수 있음!)
따라서 Activity의 수명주기, UI기반의 클래스인 점을 고려 하며 Activity의 관심사 분리 가 필요합니다.

ViewModel

안드로이드에서는 앞서말한 관심사 분리! 를 위해 ViewModel 클래스를 만들어 비즈니스 로직을 따로 처리해줍니다!

(ViewModel,Activity의 생명주기)

당장 내가 만든 임시 데이터를 버튼을 통해 화면을 띄웠다 쳐봐도 앱의 화면을 세로에서 가로로 돌려버리면 activity가 onPause()했다 다시 onStart()를 하는데 이때 다시 초기값을 돌아가 버리는 상황이 생깁니다..
(궁금하면 직접 해보심 알수 있습니다)
그렇기에 ViewModel은 단순히 관심사 분리 뿐 아니라 Andorid LifeCycle에 관련해서도 유용한 라이브러리입니다 .

MVVM패턴 사용방법

구조는 Model - View - ViewModel을 만들 것이고!
Model로는 전 시간 사용한 Retrofit2 사용할 겁니닷! LocalDB를 사용하셔도 되구 데이터라면 다 가능합니다!!

출처: https://velog.io/@ha_jni/MVVM-%ED%8C%A8%ED%84%B4%EA%B3%BC-AAC

위 다이어그램은 MVVM의 과정을 보여주는 것이며 대게는 Model과 ViewModel사이에 Repository를 만들어 통신 호출을 ViewModel에서 하지않고 Repository에서 따로 해줍니다

Repository 패턴


데이터 출처(로컬 DB인지 API응답인지 등)와 관계 없이 동일 인터페이스로 데이터에 접속할 수 있도록 만들어 ViewModel은 비즈니스로직에 집중하도록 합니다!

때문에 View - ViewModel - (Repository) - Model 중간에 Repository도 살짝 추가해보겠습니다

   //viewModel
  implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
  implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'

- ViewModel

class ViewModelGameList : ViewModel() {
    //외부에서 liveData로 넣어 접근 못 하게, Mutable 형식으로 내부에선 읽쓰 가능
    private val _liveData = MutableLiveData<List<GameResult>?>()
    val liveData: LiveData<List<GameResult>?> = _liveData

    private val getListRepository = GetListRepository()


    // 초기값 설정
    init {
        _liveData.value = listOf()
    }


    fun gameList(sharedPref: PreferenceUtil) {

        viewModelScope.launch {
            val listResponse =
                getListRepository.getGameList(sharedPref.getString(PreferenceUtil.ACCESS_TOKEN, ""),sharedPref)
            Log.e("VIEWMODEL ", "GOOOD?")

            //실패시 토큰 만료 or 통신실패
            if (listResponse.isSuccessful) {
                _liveData.value = listResponse.body()?.result
                Log.e("VIEWMODEL : SUCCESS", "${_liveData.value}")
                Log.e("VIEWMODEL : CODE", "${listResponse.code()}")
            }
        }

    }
}

아까 말한 비즈니스 로직, View의 데이터 상태를 바꿀 변수는 뭔지 보시며 이해하면 좋을 것 같습니다!

-Activity

class InfoActivity : AppCompatActivity() {

    lateinit var binding: UserInformBinding
    val viewModel: ViewModelGameList by viewModels()
   
    /*   viewModel과는 상관 없는 코드
    val sharedPref :PreferenceUtil by lazy {
        PreferenceUtil(this)
    }*/

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.user_inform)
        
        // viewModel 비즈니스 로직
         viewModel.gameList(sharedPref)
       // viewModel이 갖고 있는 값을 보여주고 싶다면
         viewModel.liveData.observe(viewLifecycleOwner) {
            val data = liveData.value
           Log.e("SHOW DATA", $data)
        }
  }
}

여기서 만약에 by viewModels()를 사용한다면 추가해주자

  implementation 'androidx.activity:activity-ktx:1.1.0'
  implementation 'androidx.fragment:fragment-ktx:1.2.5'

by viewModels()를 사용하지 않고 초기화 하려면

val viewModel: ViewModelGameList

이렇게 해주면 되는데 이때는 사용법이 좀 다르다 자세한건 요기에!!
by viewModels()가 ViewModelProvider를 초기화시점에 생성해줘 위에선 ViewModelProvider를 생략한 코드가 된것!

class InfoActivity : AppCompatActivity() {
    private lateinit var viewModel:ViewModelGameList 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //ViewModel 인스턴스 생성
        viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(ViewModelGameList ::class.java)
    }
}

이게 끝났다면!!! MVVM구조는 일단 끝이 난곤데..

Repository를 빼먹으면 섭하지만 이건 별거 없다

class GetListRepository {

    suspend fun getGameList(access: String, pref: PreferenceUtil): Response<GameResultData> {
        val gameListData = Http.preference(pref).getGameList(
            "Bearer $access", GameListReqeust("RCT")
        )
        return gameListData
    }
}

그저 viewModel에 있을 retrofit 통신 코드를 따로 빼준고다..!

profile
디그다 디그다 (끙챠끙챠)

0개의 댓글