Bradleycorn
11/26/2020, 2:31 PM@Composable
in a ViewModel? I have a block of code that produces a State
(by calling some methods on my ViewModel) … I was thinking of extracting the block into a method in the ViewModel itself, but that would mean I have a composable in the ViewModel.allan.conda
11/26/2020, 2:32 PMmagnumrocha
11/26/2020, 2:34 PMBradleycorn
11/26/2020, 2:39 PMval (post) = produceUiState(postsRepository, postId) {
getPost(postId)
}
I’m doing something similar, though with a viewmodel (which has a Repo injected), and a few more calls …
val (post) = produceUiState(viewModel, postId) {
// ... call some other viewModel methods as well
getPost(postId)
}
I was thinking of extracting it to the viewModel itself so my “Screen” Composable would be:
val (post) = viewModel.loadPost(postId)
and My ViewModel would contain:
@Composable
fun loadPost(postId: Int): UiState<Post> {
return produceUiState(postId) {
// ... call some other viewModel methods as well
getPost(postId)
}
}
Not exact, but gives an idea of what I’m thinking …allan.conda
11/26/2020, 2:43 PMallan.conda
11/26/2020, 2:43 PMallan.conda
11/26/2020, 2:44 PMval state by viewModel.state.observeAsState()
magnumrocha
11/26/2020, 2:47 PMBradleycorn
11/26/2020, 2:50 PMBradleycorn
11/26/2020, 2:51 PMallan.conda
11/26/2020, 2:53 PMBradleycorn
11/26/2020, 3:18 PMBradleycorn
11/26/2020, 4:12 PMfun loadPost(postId: Long, composableScope: CoroutineScope): State<UiState<BlogPost>> {
val state = mutableStateOf(UiState<BlogPost>(loading = true))
//Use a passed in Coroutine Scope.
//We don't want to use the viewModelScope here because the ViewMoel
//is scoped to the Activity, and we want this coroutine to be scoped
//to the composable that called this method.
composableScope.launch {
try {
// get a Flow that emits whenever the post changes in the data store.
ksrRepo.getPost(postId).distinctUntilChanged().collect {post ->
Log.d("STATE_TEST", "Post Emitted: $postId")
state.value = state.value.copy(loading = false, data = post)
}
} catch (ex: Exception) {
state.value = state.value.copy(loading = false, exception = ex)
}
}
return state
}
And then, a “Screen” composable that uses it:
@Composable
fun PostScreen(viewModel: MainViewModel, postId: Long) {
// coroutine scope that will cancel when this composable is removed
val postScreenScope = rememberCoroutineScope()
val post by remember(postId) { viewModel.loadPost(postId, postScreenScope) }
when {
post.loading -> Text("Loading Post .... ")
post.hasError -> Text("Uh Oh!")
post.data != null -> PostContent(post.data as BlogPost)
}
}
Adam Powell
11/26/2020, 4:20 PMproduceState
compose api in intent. produceState
will be better behaved around launching and cancellation than the formulation above thoughAdam Powell
11/26/2020, 4:21 PMpostId
changes, but produceState
willBradleycorn
11/26/2020, 7:59 PMproduceState
seems to be A.) a bit more robust, and B.) already written, so I don’t have to write code like the above all the time. But produceState
is @Composable
, so if you want to use it IN the ViewModel, then the ViewModel method that uses it needs to be @Composable
. Is that acceptable? Cause it feels like code smell…Adam Powell
11/26/2020, 8:08 PMval myState by produceState(initial, viewModel) {
value = viewModel.someSuspendingMethod()
}
from inside your composable as the bridgeBradleycorn
11/26/2020, 8:19 PMAdam Powell
11/26/2020, 8:23 PM@Composable
things a layer away from the VM