[Android App Architecture] UI Layer

g6y116Β·2023λ…„ 4μ›” 9일

Android App Architecture

λͺ©λ‘ 보기

πŸ’‘ν•΄λ‹Ή 글은 μ•± μ•„ν‚€ν…μ²˜ κ°€μ΄λ“œλ₯Ό μ΄ν•΄ν•˜κΈ° μ‰½κ²Œ μ •λ¦¬ν•œ κΈ€μž…λ‹ˆλ‹€.

이 글은 λ‹€μŒκ³Ό 같은 μ„ μˆ˜μ§€μ‹μ„ ν•„μš”λ‘œν•©λ‹ˆλ‹€.

  • MVVM
  • Coroutine
  • Flow
  • LiveData
  • StateFlow

UI Layer

UI LayerλŠ” Data Layerμ—μ„œ κ°€μ Έμ˜¨ 데이터λ₯Ό μƒνƒœλ‘œ μ €μž₯ν•˜κ³  화면을 λ Œλ”λ§ν•˜λŠ” μ˜μ—­μž…λ‹ˆλ‹€.

이벀트(클릭 μ΄λ²€νŠΈλ‚˜ μΉ΄μΉ΄μ˜€ν†‘ 메세지와 같은 λ„€νŠΈμ›Œν¬ 응닡)λ₯Ό μ²˜λ¦¬ν•˜κ³  μƒνƒœκ°€ λ³€κ²½λ˜λ©΄ 화면이 μžλ™μœΌλ‘œ μ—…λ°μ΄νŠΈ λ˜μ–΄μ•Όν•©λ‹ˆλ‹€.

State Holder(ViewModel)λŠ” 화면을 κ·Έλ¦¬λŠ”λ° ν•„μš”ν•œ λͺ¨λ“  μƒνƒœλ₯Ό 가지고 μžˆμ–΄μ•Όν•©λ‹ˆλ‹€.

(μ—¬κΈ°μ—μ„œ μƒνƒœλž€, ViewModelμ—μ„œ 가지고 μžˆλŠ” λ³€μˆ˜λ“€μ΄λΌκ³  이해해도 λ‹Ήμž₯은 크게 λ¬Έμ œλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.)

ν•˜μ§€λ§Œ 일반적으둜 Data Layerμ—μ„œ κ°€μ Έμ˜¨ λ°μ΄ν„°λŠ” ν™”λ©΄μ—μ„œ ν•„μš”λ‘œν•˜λŠ” 데이터와 μ™„μ „νžˆ κ°™μ§€λŠ” μ•Šμ€ ν˜•μ‹μž…λ‹ˆλ‹€. μ‹€μ œλ‘œ λ‹€μ–‘ν•œ 데이터λ₯Ό μ‘°ν•©ν•΄μ„œ μ‚¬μš©ν•΄μ•Όν•˜λŠ” κ²½μš°κ°€ λ§ŽμŠ΅λ‹ˆλ‹€. λ˜ν•œ μ •λ ¬ 쑰건과 같이 UI Elements(Activity, Fragment)μ—μ„œ λ°›μ•„μ˜€λŠ” 데이터도 μ‘΄μž¬ν•©λ‹ˆλ‹€.

UI Layer의 λ™μž‘ 방식

UI LayerλŠ” λ‹€μŒμ˜ 과정을 반볡적으둜 μ‹€ν–‰ν•©λ‹ˆλ‹€.

  1. Data Layerμ—μ„œ κ°€μ Έμ˜¨ 데이터λ₯Ό State Holder에 μƒνƒœλ‘œμ¨ μ €μž₯ν•©λ‹ˆλ‹€.
  2. State Holder(ViewModel)의 μƒνƒœλ₯Ό 기반으둜 UI Elements(Activity, Fragment)μ—μ„œ κ·Έλ €μ€λ‹ˆλ‹€.
  3. 이벀트λ₯Ό μ²˜λ¦¬ν•˜κ³  μƒνƒœκ°€ λ³€κ²½λ˜λ©΄ 화면을 μžλ™μœΌλ‘œ μ—…λ°μ΄νŠΈ ν•©λ‹ˆλ‹€.
  4. 1 ~ 3번의 과정을 λ°˜λ³΅ν•©λ‹ˆλ‹€.

μ΄μ œλΆ€ν„°λŠ” ν•΄λ‹Ή λ™μž‘λ°©μ‹μ„ ꡬ체적으둜 μ–΄λ–»κ²Œ κ΅¬ν˜„ν•˜λŠ”μ§€ μ•Œμ•„λ³΄λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€.


data class NewsUiState(
    val isSignedIn: Boolean = false,
    val isPremium: Boolean = false,
    val newsItems: List<NewsItemUiState> = listOf(),
    val userMessages: List<Message> = listOf()
data class NewsItemUiState(
    val title: String,
    val body: String,
    val bookmarked: Boolean = false,
class NewsViewModel(...) : ViewModel() {

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()



ViewModelμ—μ„œ λ³€κ²½ κ°€λŠ₯ν•œ μŠ€νŠΈλ¦Όμ„ μƒμ„±ν•˜κ³  λ³€κ²½ λΆˆκ°€λŠ₯ν•œ 슀트림으둜 λ…ΈμΆœν•©λ‹ˆλ‹€.
μ΄λ ‡κ²Œν•˜λŠ” μ΄μœ λŠ” Activity, Fragmentμ—μ„œ μž„μ˜λ‘œ λ³€κ²½ν•˜λŠ” 것을 λ°©μ§€ν•˜κΈ° μœ„ν•¨μž…λ‹ˆλ‹€.
StateFlow λŒ€μ‹  LiveData둜 κ΅¬ν˜„ κ°€λŠ₯ν•˜μ§€λ§Œ, StateFlow둜 DataBinding이 κ°€λŠ₯해진 μ΄ν›„λ‘œ, 점차 StateFlowκ°€ 많이 μ“°μ΄λŠ” μΆ”μ„Έμž…λ‹ˆλ‹€.

μƒνƒœλŠ” State Holder(ViewModel)에 λΆˆλ³€μ„±(val)으둜 μ €μž₯λ©λ‹ˆλ‹€. λ³€κ²½ λΆˆκ°€λŠ₯ν•œ 객체가 μˆœκ°„μ˜ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μƒνƒœλ₯Ό 보μž₯ν•˜κΈ° λ•Œλ¬Έμ— UI Elements(Activity, Fragment)λŠ” 화면을 μ—…λ°μ΄νŠΈν•˜λŠ” ν•œ 가지 μ—­ν™œμ— 집쀑할 수 μžˆμŠ΅λ‹ˆλ‹€.

λ˜ν•œ, 리슀트 μ •λ ¬ 방법과 같이 UI Elementsμ—μ„œλ§Œ μˆ˜μ •λ  수 μžˆλŠ” 데이터λ₯Ό μ œμ™Έν•˜κ³ λŠ” UI Elements(Activity, Fragment)μ—μ„œ μƒνƒœλ₯Ό 직접 μˆ˜μ •ν•΄μ„œλŠ” μ•ˆλ©λ‹ˆλ‹€. 이 원칙을 μœ„λ°˜ν•˜λ©΄ μ—¬λŸ¬ 데이터 μ†ŒμŠ€μ—μ„œμ˜ 데이터 λΆˆμΌμΉ˜μ™€ λ―Έμ„Έν•œ 버그가 λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μƒνƒœμ˜ 이름 지정 κ·œμΉ™

κ³΅μ‹λ¬Έμ„œμ˜ κ°€μ΄λ“œμ—μ„œλŠ” ν™”λ©΄μ˜ κΈ°λŠ₯μ΄λ‚˜ λ¬˜μ‚¬λ˜λŠ” ν™”λ©΄μ˜ 뢀뢄에 따라 μƒνƒœ 클래슀의 이름을 λ‹€μŒκ³Ό 같이 μ§€μ •ν•˜λ„λ‘ ꢌμž₯ν•©λ‹ˆλ‹€.

κΈ°λŠ₯ + UiState

ex) λ‰΄μŠ€λ₯Ό ν‘œμ‹œν•˜λŠ” ν™”λ©΄μ˜ μƒνƒœ : NewsUiState
ex) λ‰΄μŠ€ ν•­λͺ©μ˜ μƒνƒœ : NewsItemUiState

단방ν–₯ 데이터 νλ¦„μœΌλ‘œ μƒνƒœ 관리

μƒνƒœκ°€ μ•„λž˜λ‘œ ν–₯ν•˜κ³  μ΄λ²€νŠΈλŠ” μœ„λ‘œ ν–₯ν•˜λŠ” νŒ¨ν„΄μ„ 단방ν–₯ 데이터 흐름(UDF)이라고 ν•©λ‹ˆλ‹€.
이 νŒ¨ν„΄μ΄ μ•± μ•„ν‚€ν…μ²˜μ— λ―ΈμΉ˜λŠ” 영ν–₯은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • State Holder(ViewModel)λŠ” ν™”λ©΄μ˜ λ Œλ”λ§μ— μ‚¬μš©λ  μƒνƒœλ₯Ό λ³΄μœ ν•˜κ³  λ…ΈμΆœν•©λ‹ˆλ‹€.
  • μƒνƒœλŠ” State Holder(ViewModel)에 μ˜ν•΄ λ³€ν™˜λœ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ°μ΄ν„°μž…λ‹ˆλ‹€.
  • UI Elements(Activity, Fragment)μ—μ„œ μ΄λ²€νŠΈκ°€ λ°œμƒν•˜λ©΄ State Holder(ViewModel)에 μ•Œλ¦½λ‹ˆλ‹€.
  • State Holder(ViewModel)κ°€ 이벀트λ₯Ό μ²˜λ¦¬ν•˜κ³  μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€.
  • μ—…λ°μ΄νŠΈλœ μƒνƒœκ°€ λ Œλ”λ§ν•  UI Elements(Activity, Fragment)에 λ‹€μ‹œ μ œκ³΅λ©λ‹ˆλ‹€.
  • μƒνƒœμ˜ 변경을 λ°œμƒμ‹œν‚€λŠ” λͺ¨λ“  μ΄λ²€νŠΈμ— μœ„μ˜ μž‘μ—…μ΄ λ°˜λ³΅λ©λ‹ˆλ‹€.

단방ν–₯ 데이터 흐름을 μ‚¬μš©ν•˜λŠ” 이유

  • 데이터 일관성: UI용 정보 μ†ŒμŠ€κ°€ ν•˜λ‚˜μž…λ‹ˆλ‹€.
  • ν…ŒμŠ€νŠΈ κ°€λŠ₯μ„±: μƒνƒœλ₯Ό ν™”λ©΄κ³Ό λ³„κ°œλ‘œ ν…ŒμŠ€νŠΈν•  수 μžˆμŠ΅λ‹ˆλ‹€.
    즉, 화면이 없어도 μƒνƒœλ₯Ό μ •μƒμ μœΌλ‘œ κ°€μ Έμ˜€λŠ”μ§€ ν…ŒμŠ€νŠΈν•  수 μžˆμŠ΅λ‹ˆλ‹€.
    State Holder(ViewModel)λŠ” UI Elements(Activity, Fragment)κ°€ 없어도 λ™μž‘ν•˜λŠ”λ° λ¬Έμ œκ°€ μ—†μŠ΅λ‹ˆλ‹€.
  • μœ μ§€ 관리성: μƒνƒœμ˜ 변경은 잘 μ •μ˜λœ νŒ¨ν„΄μ„ λ”°λ¦…λ‹ˆλ‹€. 즉, 이벀트 및 데이터λ₯Ό κ°€μ Έμ˜¨ μ†ŒμŠ€ λͺ¨λ‘μ˜ 영ν–₯을 λ°›μŠ΅λ‹ˆλ‹€.

μƒνƒœμ˜ μ‚¬μš©

UI Elements(Activity, Fragment)μ—μ„œ μƒνƒœλ₯Ό μ‚¬μš©ν•˜λ €λ©΄ κ΄€μ°° κ°€λŠ₯ν•œ 데이터 ν™€λ”μ˜ 터미널 μ—°μ‚°μžλ₯Ό μ΄μš©ν•΄μ•Όν•©λ‹ˆλ‹€.

LiveData의 경우 observe()ν•¨μˆ˜κ°€ 있고 StateFlow의 경우 collect()ν•¨μˆ˜λ‚˜ 이λ₯Ό ν™•μž₯ν•œ ν•¨μˆ˜λ“€μ„ μ‚¬μš©ν•©λ‹ˆλ‹€.

UI Elements(Activity, Fragment)μ—μ„œ κ΄€μ°° κ°€λŠ₯ν•œ 데이터 홀더λ₯Ό μ‚¬μš©ν•  λ•ŒλŠ” UI Elements(Activity, Fragment)의 수λͺ… μ£ΌκΈ°λ₯Ό κ³ λ €ν•΄μ•Ό ν•©λ‹ˆλ‹€. 수λͺ… μ£ΌκΈ°λ₯Ό κ³ λ €ν•΄μ•Ό ν•˜λŠ” μ΄μœ λŠ” μ‚¬μš©μžμ—κ²Œ 화면이 ν‘œμ‹œλ˜μ§€ μ•Šμ„ λ•Œ UI Elements(Activity, Fragment)κ°€ μƒνƒœλ₯Ό κ΄€μ°°ν•΄μ„œλŠ” μ•ˆ 되기 λ•Œλ¬Έμž…λ‹ˆλ‹€.

LiveData의 경우 LifecycleOwnerκ°€ 수λͺ… 주기의 문제λ₯Ό μ•”μ‹œμ μœΌλ‘œ μ²˜λ¦¬ν•©λ‹ˆλ‹€.
StateFlow의 경우 μ μ ˆν•œ 코루틴 λ²”μœ„μ™€ repeatOnLifecycle API둜 μ²˜λ¦¬ν•˜λŠ” 것이 κ°€μž₯ μ’‹μŠ΅λ‹ˆλ‹€.

class NewsActivity : AppCompatActivity() {

    private val viewModel: NewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements

진행 쀑인 μž‘μ—… ν‘œμ‹œ

UiState 클래슀의 λ‘œλ“œ μƒνƒœλ₯Ό λ‚˜νƒ€λ‚΄λŠ” κ°„λ‹¨ν•œ 방법은 Boolean 값을 μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

data class NewsUiState(
    val isFetchingArticles: Boolean = false,

class NewsActivity : AppCompatActivity() {

    private val viewModel: NewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Bind the visibility of the progressBar to the state
                // of isFetchingArticles.
                    .map { it.isFetchingArticles }
                    .collect { progressBar.isVisible = it }

화면에 였λ₯˜ ν‘œμ‹œ

진행 쀑인 μž‘μ—… ν‘œμ‹œμ™€ λΉ„μŠ·ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ 였λ₯˜μ—λŠ” μ‚¬μš©μžμ—κ²Œ λ‹€μ‹œ μ „λ‹¬ν•˜λŠ” κ΄€λ ¨ λ©”μ‹œμ§€ λ˜λŠ” μ‹€νŒ¨ν•œ μž‘μ—…μ„ λ‹€μ‹œ μ‹œλ„ν•˜λŠ” κ΄€λ ¨ μž‘μ—…μ΄ 포함될 수 μžˆμŠ΅λ‹ˆλ‹€.

λ”°λΌμ„œ 데이터λ₯Ό κ°€μ Έμ˜€κ³  μžˆκ±°λ‚˜ κ°€μ Έμ˜€κ³  μžˆμ§€ μ•Šμ€ λ™μ•ˆ 였λ₯˜ μ»¨ν…μŠ€νŠΈμ— μ μ ˆν•œ 메타데이터λ₯Ό ν˜ΈμŠ€νŒ…ν•˜λŠ” 데이터 클래슀λ₯Ό μ‚¬μš©ν•˜μ—¬ 였λ₯˜ μƒνƒœλ₯Ό λͺ¨λΈλ§ν•΄μ•Ό ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

data class Message(val id: Long, val message: String)

data class NewsUiState(
    val userMessages: List<Message> = listOf(),


μ•„λž˜μ˜ λ‹€μ΄μ–΄κ·Έλž¨μ€ 이벀트λ₯Ό μ–΄λ–»κ²Œ μ²˜λ¦¬ν•΄μ•Ό ν•˜λŠ”μ§€μ— λŒ€ν•œ κ²°μ • νŠΈλ¦¬μž…λ‹ˆλ‹€.

  • ViewModel의 Flow와 같은 곳에 값이 λ“€μ–΄μ™”λŠ”κ°€? -> μƒνƒœ λ³€κ²½
  • UIμ—μ„œ 클릭 μ΄λ²€νŠΈκ°€ λ°œμƒν–ˆλŠ”λ° ν™”λ©΄λ§Œ λ³€κ²½(UI 둜직)ν•΄μ£Όλ©΄ λ˜λŠ”κ°€? -> ν™”λ©΄ λ³€κ²½
  • UIμ—μ„œ 클릭 μ΄λ²€νŠΈκ°€ λ°œμƒν–ˆλŠ”λ° λ°μ΄ν„°μ˜ λ³€κ²½(λΉ„μ¦ˆλ‹ˆμŠ€ 둜직)이 ν•„μš”ν•œκ°€? -> ViewModel둜 전달

이벀트의 처리

μ•„λž˜μ˜ μ˜ˆμ œλŠ” 화면을 λ³€κ²½ν•˜λŠ” 방법(UI 둜직)κ³Ό 데이터λ₯Ό λ³€κ²½ν•˜λŠ” 방법(λΉ„μ¦ˆλ‹ˆμŠ€ 둜직)을 λ³΄μ—¬μ€λ‹ˆλ‹€.

class LatestNewsActivity : AppCompatActivity() {

    private lateinit var binding: ActivityLatestNewsBinding
    private val viewModel: LatestNewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        /* ... */

        // The expand details event is processed by the UI that
        // modifies a View's internal state.
        binding.expandButton.setOnClickListener {
            binding.expandedSection.visibility = View.VISIBLE

        // The refresh event is processed by the ViewModel that is in charge
        // of the business logic.
        binding.refreshButton.setOnClickListener {

이벀트의 이름은 μ²˜λ¦¬ν•˜λŠ” μž‘μ—…μ— 따라 동사λ₯Ό 포함해 μ§€μ •ν•΄μ€λ‹ˆλ‹€.
ex) addBookmark(id), logIn(username, password)

탐색 이벀트의 처리

일반적으둜 λ²„νŠΌμ„ λˆŒλ €μ„λ•Œ ν™”λ©΄ 이동 방법은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

class LoginActivity : AppCompatActivity() {

    private lateinit var binding: ActivityLoginBinding
    private val viewModel: LoginViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        /* ... */

        binding.helpButton.setOnClickListener {
            navController.navigate(...) // Open help screen

λ§Œμ•½, 둜그인 μƒνƒœλ₯Ό ν™•μΈν•˜κ³  μžλ™μœΌλ‘œ ν™”λ©΄ 이동을 ν•˜κ³ μ‹Άλ‹€λ©΄ λ‹€μŒκ³Ό 같이 κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

class LoginActivity : AppCompatActivity() {
    private val viewModel: LoginViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        /* ... */

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { uiState ->
                    if (uiState.isUserLoggedIn) {
                        // Navigate to the Home screen.

μƒνƒœ VS 이벀트


항상 μ‘΄μž¬ν•©λ‹ˆλ‹€.
μƒνƒœ μƒμ„±μ˜ 좜λ ₯μž…λ‹ˆλ‹€.


μΌμ‹œμ μ΄κ³  μ˜ˆμΈ‘ν•  수 μ—†μœΌλ©° 일정 κΈ°κ°„ μ‘΄μž¬ν•©λ‹ˆλ‹€.
μƒνƒœ μƒμ„±μ˜ μž…λ ₯μž…λ‹ˆλ‹€.

μœ„ λ‚΄μš©μ„ ν•œλ§ˆλ””λ‘œ μš”μ•½ν•˜λ©΄ μƒνƒœλŠ” μ‘΄μž¬ν•˜κ³  μ΄λ²€νŠΈλŠ” λ°œμƒν•œλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€. μ•„λž˜μ˜ λ‹€μ΄μ–΄κ·Έλž¨μ€ νƒ€μž„λΌμΈμ—μ„œ μ΄λ²€νŠΈκ°€ λ°œμƒν•  λ•Œμ˜ μƒνƒœ 변경을 μ‹œκ°ν™”ν•˜μ—¬ λ³΄μ—¬μ€λ‹ˆλ‹€.

λ°±κ·ΈλΌμš΄λ“œ μŠ€λ ˆλ“œμ—μ„œ μƒνƒœ λ³€κ²½

μƒνƒœμ— λŒ€ν•œ 변경은 Main λ””μŠ€νŒ¨μ²˜μ—μ„œ ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

λ”°λΌμ„œ 데이터 λ³€κ²½ μš”μ²­μ„ 보낼 λ•Œμ—λŠ” IO λ””μŠ€νŒ¨μ²˜λ‘œ 코루틴을 μ‹€ν–‰ν•˜κ³  withContext(Dispatchers.Main)λ₯Ό 톡해 μ»¨ν…μŠ€νŠΈ μŠ€μœ„μΉ­ ν›„ μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

MutableStateFlowλ₯Ό λ³€κ²½ν•  λ•Œμ—λŠ” 일반적으둜 update ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

UiState둜 κ²°ν•©

Data Layerμ—μ„œ κ°€μ Έμ˜¨ λ°μ΄ν„°λŠ” ν™”λ©΄μ—μ„œ ν•„μš”λ‘œν•˜λŠ” 데이터와 μ™„μ „νžˆ κ°™μ§€λŠ” μ•Šμ€ ν˜•μ‹μž…λ‹ˆλ‹€.

μ΄λŸ¬ν•œ 데이터듀은 μ •λ ¬ 쑰건과 같이 UI Elements(Activity, Fragment)μ—μ„œ λ°›μ•„μ˜€λŠ” 데이터λ₯Ό ν¬ν•¨ν•˜μ—¬ μ‘°ν•©ν•΄μ„œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ•„λž˜λŠ” λ‹€μˆ˜μ˜ Flowλ₯Ό combine으둜 κ²°ν•©ν•œ ν›„ stateIn을 톡해 κ΄€μ°° κ°€λŠ₯ν•œ μƒνƒœλ‘œ λ³€κ²½ν•˜λŠ” μ˜ˆμ œμž…λ‹ˆλ‹€.

class TaskDetailViewModel @Inject constructor(
    private val tasksRepository: TasksRepository,
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val _isTaskDeleted = MutableStateFlow(false)
    private val _task = tasksRepository.getTaskStream(taskId)

    val uiState: StateFlow<TaskDetailUiState> = combine(
    ) { isTaskDeleted, task ->
            task = taskAsync.data,
            isTaskDeleted = isTaskDeleted
        // Convert the result to the appropriate observable API for the UI
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = TaskDetailUiState()

    fun deleteTask() = viewModelScope.launch {
        _isTaskDeleted.update { true }

μ°Έκ³ ν•˜λ©΄ 쒋은 자료


Android Junior Developer

0개의 λŒ“κΈ€