如何将 Compose LazyColumn 与 Coroutines/Room 数据库一起使用?

How do you use Compose LazyColumn with Coroutines/Room Database?

我正在为自己编写一个简单的应用程序,以将 RSS 提要显示为项目列表,并认为这是了解 @Compose lazyColumn 的好机会。我对它的代码如此之少感到震惊,没有 Adapter,没有 ViewHolder,没有 xml。惊人的流线型。

但是您如何设置它以使用 Room Database 之类的东西,您正在使用协程从数据库中提取数据?官方文档 here 谈到了 rememberCoroutineScope() 但没有解释你在哪里定义它。在 Surface 定义中放置协程感觉很奇怪,这可能就是它不起作用的原因。它会产生一个:@Composable invocations can only happen from the context of a @Composable function 错误。

这需要通过 ViewModel 完成吗?在现实世界中,存储库中的数据无论如何都来自 ViewModel,但现在我只想显示一个简单的从互联网上提取的项目列表(即,需要协程)。但是你如何处理来自 ViewModel/Database 的 LiveDataList<String>

有人知道如何设置吗?我觉得应该不难,但我不知道从哪里开始寻找答案。

例如:

class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           MyAppTheme {

               val composeScope = rememberCoroutineScope()
               Surface(color = MaterialTheme.colors.background) {
  
                    composeScope.launch{ // do network call
                        RssList(RssFetcher.fetchRss())  // how do you make this work?
                    }
               }
           }
       }
   }
}

@Composable
fun RssList(list: List<RssItems>){
   LazyColumn{ items(list) ... }
}
@Composable
fun TestView(
) {
    var rssList by remember { mutableStateOf(emptyList<RssItems>()) }
    LaunchedEffect(Unit) {
        rssList = RssFetcher.fetchRss()
    }
    RssList(rssList) 
}

@Composable
fun RssList(list: List<RssItems>){
    LazyColumn{ items(list) ... }
}

要了解这里发生的事情,您需要查看以下内容:

  1. 在撰写中存储状态。 remember { mutableStateOf() } 是在重组之间存储数据的最简单方法。但是当你在视图之间切换时它会被清理,所以也检查视图模型,这可能更适合你的任务。 documentation
  2. LaunchedEffect 是在可组合函数内执行任何操作的首选方式。在此块中,您已经在协程中,运行 suspend 函数
  3. 也可以
  4. 你不需要为rememberCoroutineScope定义协程,它returns预初始化协程。通常你需要将它用于按钮按下或触摸等事件

创建视图模型,理想情况下,假设您可能希望保留下载的供稿以供将来使用。

class RSSViewModel : ViewModel() {
    var RSS by mutableStateOf(listOf</*Add Type Here*/>()) // could be val as well
        private set // No external modifications to protect from side-effects

    init {
        viewModelScope.launch { // Pre-defined Coroutine Scope
            RSS =
                RSSFetcher.fetchRSS() // You could also extract the logic into a method for on-demand initialisation
        }
    }

    fun refreshRSS() { // This is what I was talking about above. On-demand
        viewModelScope.launch { 
            RSS =
                RSSFetcher.fetchRSS()
        }
    }
}

现在,注意我们已经声明了 MutableState<T> 类型的变量,这在 Compose 中是触发重组所必需的(您也可以使用 LiveDataFlow,但我强烈建议使用尽你所能,提供高可靠性)。

好了,我们都设置到这里了,现在只需将代码添加到 activity

class MainActivity : ComponentActivity() {
    private val viewModel by viewModels<RSSViewModel>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyAppTheme {
               RSSList(viewModel.RSS) // Automatic Recompositions on Refresh, all done!
            }
        }
    }
}

是的,

编辑:如果你想从房间数据库中提取提要,如果你return原样的原始数据,那将是一团糟。 Exceptions 会说你不能在主线程上调用它;然后在整个地方添加那些 thread(){...} 电话,这使它看起来比你的房间更乱。为此,改为 return LiveData。令人惊讶的是,如果你return LiveData 从数据库查询,你实际上可以调用主线程上的方法。清洁。