如何在Activity中不观察ViewModel viewModelScope.launch中的值?

huangapple go评论65阅读模式
英文:

How to get value from ViewModel viewModelScope.launch without observing it in Activity?

问题

I am in the process to re-build my live app to use MVVM and Coroutines and moving most of the code that is not UI related from Activity/Fragment to ViewModel.

目前,我的应用程序在活动和片段中使用Java语言,而在新代码中使用Kotlin。该应用程序的最低Sdk版本为19

在某些情况下,我在我的应用程序中实现了LiveData和LiveData Observer,但大多数情况下,我需要在viewModelScope.launch中从ViewModel中获取当前值,而不是在Activity/Fragment中使用Observer。

出于演示目的,我创建了一个小应用程序,展示了我迄今为止尝试的内容。我在Activity中使用日志(Timber.d)来显示结果。不幸的是,结果显示了默认值,而不是我期望的新值(10和30)。

我的目标是在Activity中获取当前值(10和30),而不使用Observer。

ViewModel - Kotlin

class MainActivityViewModel(
    private val mainActivityRepository: MainActivityRepository = MainActivityRepositoryImpl(),
    private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
    private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main
) : ViewModel() {
    ...
    private val _flowWithReturnAndDispatcher = MutableStateFlow<Int?>(-1)
    val flowWithReturnAndDispatcher = _flowWithReturnAndDispatcher.asStateFlow()
    private suspend fun getFlowWithReturnAndDispatcher(): Flow<Int> {
        return flow {
            val data = 10
            emit(data)
        }.flowOn(ioDispatcher)
    }

    private val _mFlowValue = MutableStateFlow<Int?>(-1)
    val mFlowValue = _mFlowValue.asStateFlow()
    private val flowValue: Flow<Int> = flow {
        val data = 30
        emit(data)
    }

    fun viewModelScopeCollectFlow() {
        viewModelScope.launch {
            val mFlowWithReturnAndDispatcher = getFlowWithReturnAndDispatcher()
            val mFlowValue = flowValue

            mFlowWithReturnAndDispatcher.collect { value ->
                _flowWithReturnAndDispatcher.value = value
            }

            flowValue.collect { value ->
                _mFlowValue.value = value
            }
        }
    }
    ...
}

Activity - Java

public class MainActivity extends AppCompatActivity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        viewModel.viewModelScopeCollectFlow();

        Timber.d("onCreate-> flowWithReturnAndDispatcher: " +
                viewModel.getFlowWithReturnAndDispatcher().getValue()  + "" +
                "\nflowValue: " + viewModel.getMFlowValue().getValue());
        ...
    }
    ...
}

请注意,上述代码中的注释部分没有进行翻译,因为您要求只翻译代码部分。

英文:

I am in the process to re-build my live app to use MVVM and Coroutines and moving most of the code that is not UI related from Activity/Fragment to ViewModel.

Currently, my app uses Java language for Activities and Fragment and Kotlin for everything new. The app minimum Sdk Version is 19.

In some cases, I implemented LiveData and Live Data Observer in my app, however most of the time I need to get the current value from ViewModel inside viewModelScope.launch without using the Observer in the Activity/Fragment.

For Demonstration purposes, I created a small app that shows what I tries so far. I am using a log (Timber.d) to show the result in Activity. Unfortunately, the result shows the default values instead of the new values that I expected (10 and 30).

My goal is to get the current value (10 and 30) in my Activity without using Observer.

ViewModel - Kotlin

class MainActivityViewModel(
    private val mainActivityRepository: MainActivityRepository = MainActivityRepositoryImpl(),
    private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
    private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main
) : ViewModel() {
...
  private val _flowWithReturnAndDispatcher = MutableStateFlow&lt;Int?&gt;(-1)
    val flowWithReturnAndDispatcher = _flowWithReturnAndDispatcher.asStateFlow()
    private suspend fun getFlowWithReturnAndDispatcher():Flow&lt;Int&gt;{
        return flow {
            val data = 10
            emit(data)
        }.flowOn(ioDispatcher)
    }


    private val _mFlowValue = MutableStateFlow&lt;Int?&gt;(-1)
    val mFlowValue = _mFlowValue.asStateFlow()
    private val flowValue: Flow&lt;Int&gt; = flow {
        val data = 30
            emit(data)
    }


fun viewModelScopeCollectFlow() {
        viewModelScope.launch {
            val mFlowWithReturnAndDispatcher =             getFlowWithReturnAndDispatcher()
            val mFlowValue = flowValue

            mFlowWithReturnAndDispatcher.collect { value -&gt;
                _flowWithReturnAndDispatcher.value = value
            }

            flowValue.collect { value -&gt;
                _mFlowValue.value = value
            }

        }
    }
...
}

Activity - Java

public class MainActivity extends AppCompatActivity {
...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
...
    viewModel.viewModelScopeCollectFlow();

  Timber.d(&quot;onCreate-&gt; flowWithReturnAndDispatcher: &quot; +
                viewModel.getFlowWithReturnAndDispatcher().getValue()  + &quot;&quot; +
                &quot;\nflowValue: &quot; + viewModel.getMFlowValue().getValue());
...
}
...
}

答案1

得分: 1

In your Activity, the problem is that you're trying to get the values instead of observing them. Rarely (or never) should you use the value of a StateFlow outside the ViewModel. Flows are intended for collecting. When you try to immediately get the value instead of observing, you're getting the value before they have had a chance to be updated.

Since your Activity is in Java, you should convert your flows into LiveData and observe it in the Activity instead of using getValue().

This:

    private suspend fun getFlowWithReturnAndDispatcher(): Flow<Int> {
        return flow {
            val data = 10
            emit(data)
        }.flowOn(ioDispatcher)
    }

is wrong in a few ways. First, you don't need to suspend to create a Flow. Second, it's against Kotlin convention to create a getter function instead of using a property. Third, there's no need to specify a dispatcher if you aren't calling blocking code or code that requires the main thread (like using liveData.value =).

Your viewModelScopeCollectFlow() function is convoluted design. You shouldn't have to manually tell the ViewModel to start preparing its own stuff. I would recommend changing it to an init block, but even that is really convoluted. You could just be using stateIn to greatly simplify your code.

class MainActivityViewModel(
    private val mainActivityRepository: MainActivityRepository = MainActivityRepositoryImpl(),
    private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
    private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main
) : ViewModel() {
...

    private val baseFlowWithReturnAndDispatcher: Flow<Int> {
        return flow {
            val data = 10
            emit(data)
        }
    }
    val flowWithReturnAndDispatcher = baseFlowWithReturnAndDispatcher
        .asLiveData()

    private val baseFlowValue: Flow<Int> = flow {
        val data = 30
            emit(data)
    }
    val flowValue = baseFlowValue
        .asLiveData()
}

But since you're collecting from a Java class, you should use LiveData instead of StateFlow:


Side note, you keep naming things "mSomething" incorrectly. "m" stands for "member" in Hungarian notation (which almost no one uses any more because it's generally considered bad for readability and unnecessary in modern IDEs). "Member" means not a local variable, but a variable that is defined for the whole class scope. If we were to use it in Kotlin, it would only be sensible to use for properties inside classes.

英文:

In your Activity, the problem is that you're trying to get the values instead of observing them. Rarely (or never) should you use the value of a StateFlow outside the ViewModel. Flows are intended for collecting. When you try to immediately get the value instead of observing, you're getting the value before they have had a chance to be updated.

Since your Activity is in Java, you should convert your flows into LiveData and observe it in the Activity instead of using getValue().

This:

    private suspend fun getFlowWithReturnAndDispatcher():Flow&lt;Int&gt;{
        return flow {
            val data = 10
            emit(data)
        }.flowOn(ioDispatcher)
    }

is wrong in a few ways. First, you don't need to suspend to create a Flow. Second, it's against Kotlin convention to create a getter function instead of using a property. Third, there's no need to specify a dispatcher if you aren't calling blocking code or code that requires the main thread (like using liveData.value =).

Your viewModelScopeCollectFlow() function is convoluted design. You shouldn't have to manually tell the ViewModel to start preparing its own stuff. I would recommend changing it to an init block, but even that is really convoluted. You could just be using stateIn to greatly simplify your code.

class MainActivityViewModel(
    private val mainActivityRepository: MainActivityRepository = MainActivityRepositoryImpl(),
    private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
    private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main
) : ViewModel() {
...


    private val baseFlowWithReturnAndDispatcher: Flow&lt;Int&gt; {
        return flow {
            val data = 10
            emit(data)
        }
    }
    val flowWithReturnAndDispatcher = baseFlowWithReturnAndDispatcher
        .asLiveData()

    private val baseFlowValue: Flow&lt;Int&gt; = flow {
        val data = 30
            emit(data)
    }
    val flowValue = baseFlowValue
        .asLiveData()
}

But since you're collecting from a Java class, you should use LiveData instead of StateFlow:


Side note, you keep naming things "mSomething" incorrectly. "m" stands for "member" in Hungarian notation (which almost no one uses any more because it's generally considered bad for readability and unnecessary in modern IDEs). "Member" means not a local variable, but a variable that is defined for the whole class scope. If we were to use it in Kotlin, it would only be sensible to use for properties inside classes.

huangapple
  • 本文由 发表于 2023年4月13日 19:02:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76004655.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定