본문 바로가기

Android/Architecutre

[안드로이드 MVVM] Repository 에서 ViewModel, View(Activity,Fragment)에 Event/Data 전달하기 ― Repository에서의 LiveData ?

728x90
반응형

 

 

LiveData를 이용한 데이터 Observing & View Update

MVVM 구조에서 Repository에서 ViewModel의 메서드를, ViewModel에서 Activity등의 view를 참조하면 안되었습니다.

위 그림의 구조처럼 View는 ViewModel을 계속 관찰, ViewModel은 Repository의 데이터를 계속 관찰하여 View를 업데이트 해야합니다.

ViewModel에서 데이터 변화에 따라 View를 변화시키고 싶을 때, Event를 발생하고 싶을 때 Event Wrapper를 사용해야 한다고 포스팅 하였습니다.

[Android/이론] - ViewModel과 View(Activity, Fragment)간의 이벤트 처리― Event Wrapper 사용하기

Event Wrapper사용이 궁금하면 위의 글을 꼭 참조해주세요.

 

이 글은 Event Wrapper를 사용하여 ViewModel뿐 아니라 Repository에서 발생한 Event를 View(Activity, Fragment)에서 처리하기 위함으로 위 글의 연장선 입니다.

 

Repository에서 모듈이 Event를 뿌리고, View의 text를 업데이트 하고 싶습니다. 

Repository에서 모듈이 시작되었다는 event와 string을 보내줄 것입니다.

class MyRepository{
...
	var goToLocationStart = MutableLiveData<Event<String?>>()
    ...

   fun onGoToLocationStatusChanged(location: String) {
       ...
       goToLocationStart.postValue(Event(location)) //Event
       ...
   }
}

 

밑과 같이 ViewModel에서 LiveData로 변수를 관찰합니다.

class MyViewModel(private val repository: MyRepository): ViewModel() {
   ...
	val goToLocationStart: LiveData<Event<String?>>
        get() = repository.goToLocationStart
   ...
}

 

View(Activity)에서 데이터 변화 이벤트를 감지하여, 처리할 수 있습니다.

class MyActivity: AppCompatActivity(){
   private val viewModel by viewModel<MyViewModel>()
   
   override fun onCreate(savedInstanceState: Bundle>) {
      super.onCreate(savedInstanceState)
      // data binding 사용
      val binding =
            DataBindingUtil.setContentView<ActivityRoadguideBinding>(this, R.layout.activity_roadguide)
      binding.viewModel = viewModel
      
   	  ...
      viewModel.goToLocationStart.observe(this, {
         it.getContentIfNotHandled()?.let{ location ->
            // event 처리
            binding.txtGoToLocation.text = location //text update
         }
      })
   }
   
   ...
}

 

viewModel.goToLocationStart.observe 부분을 참고해주세요.

위와 같이 View에서 ViewModel을 옵저버를 통해 관찰하게 됩니다.

따라서 View→ViewModel→Repository방향 관찰을 통해 Repository의 데이터 변화, 이벤트를 View에서 처리할 수 있습니다.

그러나.. 

 

Don't Use LiveData in Repositories

최근 포스팅 된 글 중 LiveData를 Repository에서 쓰지말라는 글이 있었는데요.

위 글을 참조하면, LiveData Observer들이 항상 Main Thread 에서 호출 된다는 것입니다.

이렇게 되면 main thread에서 많은 작업(장기적인 작업)이 일어날 수 있게됩니다. 

위의 포스팅에서는 이에 대한 대안이 Couroutine Flow라고 합니다. 

 

Coroutine Flow의 사용

https://proandroiddev.com/dont-use-livedata-in-repositories-f3bebe502ed3

만약 장기적인, 또는 지속적인 data를 repository에서 LiveData로 감싸 UI업데이트 하는데 사용했다면, 

당장 Flow를 사용해보세요. 저는 위의 포스팅을 읽고 바로 flow로 바꾸어 적용했더니.

말그대로 데이터 흐름(Flow)이 눈에 띄게 좋아졌습니다. (UI가 버벅거리지 않게 되었습니다.)

적용은 이렇습니다.

 

  • Repository
var txtRead: String = ""

text를 Read하는 txtRead변수를 계속 관찰하여 View에서 UI업데이트 할 것입니다.

 

var txtRead: String = ""
var isTxtRead: Boolean = false

fun fetchReadText() = flow{
   while(true) {
        if(isTxtRead) {
            emit(txtRead)
            isTxtRead = false
        }
   }
}.flowOn(Dispatchers.Default)

위와 같이 flow를 사용하여 txtRead를 계속 관찰하게됩니다.

저처럼 isTxtRead와 같은 Active 변수를 하나두어 text가 들어올때마다 read하여도 되고,

 

fun fetchReadText() = flow{
   while(true) {
      emit(txtRead)
      delay(1000)     
   }
}.flowOn(Dispatchers.Default)

delay를 사용하여 일정주기마다 계속 업데이트 하여도 됩니다.

 

  • ViewModel
val readTxt: LiveData<String>
   get() = myRepository.fetchReadText().asLiveData(viewModelScope.coroutineContext)

이를 ViewModel에서 위와 같이 LiveData로 바꾸어줄 수 있습니다.

 

  • Activity(View)
viewModel.readTxt.observe(this,{
       binding.txtRead.append(it)
      // UI Update
})

View에서 이전처럼 observe를 사용하여 UI Update가 가능하게 됩니다.

 

결론 : 저는 아직까지 Repository에서 간단한 UI Event는 LiveData를 사용하고 있습니다. 하지만,

장기적인, 지속적인 Data 는 당연히 flow를 사용해야 하겠습니다. Main Thread에서는 UI 변경만 해야 하니까요 ^^;

아직까지도 MVVM 코드의 효율적인 구성을 위해 많이 고민하고 있습니다. 좋은 의견 있으시다면 공유해주시면 감사하겠습니다.(Please~)

 

 

 

728x90
반응형