MutableStateflow와 liveData

오븐·2025년 4월 30일

안드로이드

목록 보기
8/9

지난번에 mutableStateOf을 살펴보다가, 공식문서의 다음과 같은 글을 읽었다.

Compose doesn't require that you use MutableState to hold state; it supports other observable types. Before reading another observable type in Compose, you must convert it to a State so that composables can automatically recompose when the state changes.

Compose ships with functions to create State from common observable types used in Android apps. Before using these integrations, add the appropriate artifact(s) as outlined below:

  • Flow: collectAsStateWithLifecycle()
  • collectAsStateWithLifecycle() collects values from a Flow in a lifecycle-aware manner, allowing your app to conserve app resources. It represents the latest emitted value from the Compose State. Use this API as the recommended way to collect flows on Android apps.

요컨데 다른 observable도 있다는 것이다.
그 중에 mutableStateFlow와 LiveData를 발견했고, 둘이 무엇인지 알아보려고 한다.

MutableStateFlow...를 하기 전에 Flow

  • 지속적으로 값을 반환하는 비동기적인 데이터 스트림 (코틀린에서 지원)
  • 또는, 시퀀스Sequence(값이 뒤늦게 생산되는 컬렉션 타입)의 비동기식 버전
  • 코루틴을 사용해 값을 생성하고 변경하고 읽을 수 있음
  • suspending 함수를 지원 -> map과 같은 연산자 내에서 순차적인 비동기 작업을 수행할 수 있음

뭔 소리야? 다행히도 문서가 요약을 해준다.

Flow는 값의 비동기 시퀀스입니다.

Flow는 네트워크 요청, 데이터베이스 호출, 기타 비동기 코드와 같은 값을 생성하는 비동기 작업에서 한번에 한번씩(한번에 모두를 생성하는 대신) 값을 생성합니다. Flow는 API 전체에서 코루틴을 지원하므로 코루틴을 사용하여 Flow를 변환할 수 있습니다.

이젠 알 것 같기도 하고 모르는 것 같기도 하다. 문서를 좀 더 살펴보자

fun makeFlow() = flow {
   println("sending first value")
   emit(1)
   println("first value collected, sending another value")
   emit(2)
   println("second value collected, sending a third value")
   emit(3)
   println("done")
}

scope.launch {
   makeFlow().collect { value ->
       println("got $value")
   }
   println("flow is completed")
}

결과는 다음과 같다.

sending first value
got 1
first value collected, sending another value
got 2
second value collected, sending a third value
got 3
done
flow is completed

위 코드에서
1. flow는 emit이 호출될 때 collect가 값을 받을 때까지 중단suspend한다.
2. emit의 값은 collect에서 출력한다.
3. 처리가 끝나면 중단된 부분에서 재개한다.
4. flow 빌더가 완료되면 collect는 "flow is completed"를 출력한다.

이렇게 collect를 통해서 flow의 값을 받아갈 수 있다. flow는 collect가 호출되면 실행된다. flow를 정의함으로써 실행되지 않음에 주의하자.

하지만 재생성 횟수와는 상관 없이 데이터에 계속 접근해야하는 경우가 있다. collect로 값을 가져오는 게 아닌 일종의 버퍼가 필요할 때, StateFlow를 사용한다.

StateFlow, MutableStateFlow

이를 위해 StateFlow가 있다.

  • StateFlow는 컬렉터가 없더라도 데이터를 보관. 또 모든 업데이트를 받아 최신 데이터를 저장

  • 관찰 가능한 변경 가능 상태를 유지해야하는 클래스에 아주 적합

  • 상태를 업데이트하고 흐름에 전송할 때 MutableStateOf을 사용

    class LatestNewsViewModel(
      private val newsRepository: NewsRepository
    ) : ViewModel() {
    
      // 다른 클래스에서 상태를 업데이트하는 걸 방지
      private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
      // UI가 상태를 업데이트하기 위해 이 StateFlow에서 값을 가져감
      val uiState: StateFlow<LatestNewsUiState> = _uiState
    
      init {
          viewModelScope.launch {
              newsRepository.favoriteLatestNews
                  // 최신 뉴스로 View를 업데이트
                  // MutableStateFlow에 값을 작성
                  // flow에 값을 더하고 관련 있는 모든 컬렉터에 업데이트
                  .collect { favoriteNews ->
                      _uiState.value = LatestNewsUiState.Success(favoriteNews)
                  }
          }
      }
    }
    // 다른 상태의 스크린을 표현
    sealed class LatestNewsUiState {
      data class Success(val news: List<ArticleHeadline>): LatestNewsUiState()
      data class Error(val exception: Throwable): LatestNewsUiState()
    }

    해당 viewModel은 다음과 같이 적용할 수 있다.

    class LatestNewsActivity : AppCompatActivity() {
      private val latestNewsViewModel = // getViewModel()
    
      override fun onCreate(savedInstanceState: Bundle?) {
          ...
          // 라이프사이클 범위에서 코루틴 시작
          lifecycleScope.launch {
    			// repeatOnLifecycle은 라이프사이클이 STARTED거나 그 이상일 때
    			// 새 코루틴에 해당 블록을 실행, STOPPED일 때 취소
              repeatOnLifecycle(Lifecycle.State.STARTED) {
    				// Flow를 시작하고 계속 값을 관찰              
                  latestNewsViewModel.uiState.collect { uiState ->
                      // 새 값을 받았을 때
                      when (uiState) {
                          is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                          is LatestNewsUiState.Error -> showError(uiState.exception)
                      }
                  }
              }
          }
      }
    }

LiveData

여기서 StateFlow와 많이 비교되는 것이 LiveData다.

  • 관찰 가능한 데이터 홀더 클래스
  • 액티비티, 프래그먼트, 서비스 등 다른 앱 구성요소의 수명 주기를 인식
  • 활동 수명 주기 상태에 있는 앱 구성요소 관찰자만 업데이트함

수명주기가 STARTED나 RESUMED 상태의 관찰자에게만 업데이트 정보를 알린다. 이 덕에 메모리 누수가 없고, 알아서 최신 데이터를 보장해준다.

빠른 이해를 위해 사용 방법을 알아보자.

  1. LiveData의 인스턴스를 생성
  class NameViewModel : ViewModel() {

    // LiveData 생성
    val currentName: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

    // Rest of the ViewModel...
}
  1. Observer 객체를 생성. onChanged() 메서드를 정의해 LiveData 객체가 보유한 데이터 변경 시 발생하는 작업을 제어한다.
  2. observe() 메서드를 사용해 LiveData 객체에 Observer 객체를 연결
  class NameActivity : AppCompatActivity() {

    private val model: NameViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 기타 코드...

        // 관찰자를 생성
        val nameObserver = Observer<String> { newName ->
            // TextView의 UI를 변경
            nameTextView.text = newName
        }

        // 이 액티비티를 LifecycleOwner이자 관찰자로 전달, LiveData 관찰
        model.currentName.observe(this, nameObserver)
    }
}

아래와 같은 식으로 업데이트되면 nameTextView가 업데이트된다.

  button.setOnClickListener {
    val anotherName = "John Doe"
    model.currentName.setValue(anotherName)
}

StateFlow와 차이점

문서를 통틀어 보면 가장 큰 차이점은 두 개다.
1. StateFlow는 초기값을 반드시 생성해야한다.
2. LiveData는 Observer를 반드시 생성해야한다.

어느 것이 더 낫다! 는 지금으로는 판단하기가 힘들다. 다만 글들을 찾아보면 Flow로 LiveData같은 동작을 할 수 있다고 한다. (더불어 LiveData는 Flow로 대체할 수 있지만 Flow는 LiveData로 대체할 수 없다는 글이 많다.... (참고글: 스택오버플로우의 LiveData vs StateFlow)

이 점은 좀 더 공부해보고 이야기하려고 한다

출처:

profile
하루에 한번 정권 찌르기

0개의 댓글