Reputation: 529
I'm writing a simple app for myself to display an RSS feed as a list of items and figured it'd be a good opportunity to learn about @Compose lazyColumn
. I'm blown away with how little code it is, no Adapter, no ViewHolder, no xml. Amazingly streamlined.
But how can you set it up to work with something like Room Database
where you're using a coroutine to pull data from a database? The official documentation here talks about rememberCoroutineScope()
but doesn't explain where you define it. Placing a coroutine inside the Surface definition feels weird, which is probably why it doesn't work. It yields a: @Composable invocations can only happen from the context of a @Composable function
error.
Does this need to be done through a ViewModel
? In the real world data from the repository will be coming from a ViewModel
anyway, but for now I just wanted to display a simple of list of items pulled from the internet (ie, requiring a coroutine). But then how do you handle LiveData
or List<String>
from ViewModel/Database?
Does anyone know how to set this up? I feel it shouldn't be that hard, but I don't know where to start looking for answers.
Ex:
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) ... }
}
Upvotes: 4
Views: 3106
Reputation: 6835
Create a viewmodel, ideally, assuming you might want to preserve the downloaded feed for future use.
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()
}
}
}
Now, note we have declared the variable of type MutableState<T>
which is necessary in Compose to trigger recompositions (You could also use LiveData
or Flow
, but I strongly recommend using this as much as you can, offers high reliability).
Ok, we're all set up here, now just add code to the 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!
}
}
}
}
Yep,
EDIT: If you wish to extract the feed from a Room Database though, it would be a heck of a mess if you return raw data as it is. The Exceptions
will say you cannot call this on the main thread; then add those thread(){...}
calls all over the place which makes it look messier than your room. For that, return LiveData
instead. Surprisingly, if you return LiveData
from the database queries, you actually can call the methods on the main thread. Clean.
Upvotes: 2
Reputation: 87794
@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) ... }
}
To understand what's going on here you need to check out about:
remember { mutableStateOf() }
is the simplest way of storing data between recompositions. But it'll be cleaned when you switch between views, so check out on view models too, which may suit better for your task. documentationsuspend
functionsrememberCoroutineScope
, it returns pre-initialized coroutine. Usually you need to use it for events like button press or touchUpvotes: 5