View Model with Jetpack compose view - android

I am using ViewModelFactory to obtain view model instance which is to be used by my jetpack compose view.
class AchievementsScreenViewModelFactory() :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = AchievementsScreenViewModel() as T
}
As soon as instantiate my viewmodel, i want to perform some operations. I am currently storing those operations in the viewmodel constructor(Like some firebase operation to check if the user instance is found or not).Is that a wrong practice? if so, what should i do?
constructor(context:Context) : this() {
this.context=context
mAuth= FirebaseAuth.getInstance()
if(mAuth.currentUser!=null){
triggerNavigateEvent(Screen.DashboardScreen)
}
}
So, the issue that I am facing is that, whenever I use my View Model Factory to instantiate an instance of my view and then when i pop the view from the NavController and return to it, the View Model Factory returns me the same instance of the View Model and the tasks that are present in my constructor are not being performed.
Is there a way to kill the instance of my View Model at the time of popping the screen from the NavController? or is there an other way?
I am calling the viewmodel from the composable screen like this
#SuppressLint("CoroutineCreationDuringComposition")
#Composable
fun LoginScreen(navController: NavHostController
){
var viewModel:LoginScreenViewModel= viewModel(
factory = LoginScreenViewModelFactory(LocalContext.current)
)
.
.
.
}
I am navigating to the screens using google accompanist navigation library.
AnimatedNavHost(
navController = navController,
startDestination = Screen.SplashScreen.route,
enterTransition = { fadeIn(animationSpec = tween(1000), initialAlpha = 0f) },
exitTransition ={ fadeOut(animationSpec = tween(1000), targetAlpha = 0f) }
){
composable(
route = Screen.LoginScreen.route
){
LoginScreen(navController = navController)
}
}

The navigation-compose NavHost (in your case AnimatedNavHost) will call its composablefunction for the target destination every time the destination changes, i.e. when you navigate to a destination and also when you navigate back. That means that you can put the code that you want to run into a method/function in your ViewModel (instead of its constructor) and use a LaunchedEffect composable to call it. If you use a constant key when invoking the LaunchedEffect composable, for example LaunchedEffect(Unit), it will only run once when it enters the composition, in your case once each time the destination changes.
Move the code from VM constructor to a new function in your VM
suspend fun callSomeApi() {
// your code here
}
And add a LaunchedEffect(Unit) to the composable you want to call this new function from
#Composable
fun LoginScreen(navController: NavHostController){
var viewModel: LoginScreenViewModel = viewModel(
factory = LoginScreenViewModelFactory(LocalContext.current)
)
// called once every time this composable enters the composition
LaunchedEffect(Unit) {
viewModel.callSomeApi()
}
}

Here is an example I use
val viewModel = hiltViewModel<PokemonListVm>()
Usage:
#Composable
fun PokemonListScreen(
navController: NavController
) {
val viewModel = hiltViewModel<PokemonListVm>()
val lazyPokemonItems: LazyPagingItems<PokedexListEntry> = viewModel.pokemonList.collectAsLazyPagingItems()
Surface(
color = MaterialTheme.colors.background,
modifier = Modifier.fillMaxSize()
) {
Column {
Spacer(modifier = Modifier.height(20.dp))
PokemonBanner()
PokemonSearch()
PokemonLazyList(
pokemonList = lazyPokemonItems,
onItemClick = { entry ->
navController.navigate(
"pokemon_detail_screen/${entry.dominentColor.toArgb()}/${entry.pokemonName}"
)
}
)
}
}
}

Related

How to scroll a Column to a new position whenever a ViewModel emits a new value?

I have a case where I have a ViewModel that emits scroll values to a StateFlow. Now, my Composable view should scroll to the most recent value, but I can't figure out how to achieve that.
My view model looks something like this:
class MyViewModel : ViewModel() {
private val scrollFlow = MutableStateFlow<Int>(0)
fun getScrollFlow(): StateFlow<Int> = scrollFlow.asStateFlow()
}
And my view is like this:
#Composable
fun MyScrollingView() {
val viewModel = viewModel()
val scroll by viewModel.getScrollFlow().collectAsState()
val scrollState = rememberScrollState(0)
Column(modifier = Modifier.verticalScroll(scrollState)) {
// Content here
}
}
The thing here is, how do I make the scroll state to react to the values coming from the view model?
There are many way to achieve that, for example:
val scrollState = rememberScrollState(0)
rememberCoroutineScope().launch {
viewModel.getScrollFlow()
.flowOn(Dispatchers.Default)
.onEach{scrollVal ->
scrollState.animateScrollTo(scrollVal)
}.collect()
}
Actually I'm afraid that code could be launched on every recomposition and is not ideal so maybe:
remember{
viewModel
.getScrollFlow()
.onEach{scrollVal ->
scrollState.animateScrollTo(scrollVal)
}
}.collectAsState(0, Dispatchers.Default)
or
val scroll by viewModel.getScrollFlow().collectAsState(0, Dispatchers.Default)
LaunchedEffect(scroll){
crollState.animateScrollTo(scroll)
}
You can try doing it inside a LaunchedEffect and use the scrollFlow value as its key, every time the scrollFlow emits new value, the LaunchedEffect will trigger its block.
#Composable
fun MyScrollingView() {
val viewModel = viewModel()
val scroll by viewModel.getScrollFlow().collectAsState()
val scrollState = rememberScrollState(0)
Column(modifier = Modifier.verticalScroll(scrollState)) {
// Content here
}
LaunchedEffect(scroll) {
scrollState.animateScrollTo(0)
//or
// scrollState.scrollTo(0)
}
}
I'm just not sure if this would work in your case especially having this statement inside your composable though.
val viewModel = viewModel()

Jetpack Compose Preview is not showing when having a ViewModel parameter

I am using Jetpack Compose and noticed that the preview is not shown. I read articles like this, but it seems my problem has a different root cause. Even I added defaults to all parameters in the compose function like this:
#OptIn(ExperimentalLifecycleComposeApi::class)
#Composable
#ExperimentalFoundationApi
#Preview
fun VolumeSettingsScreen(
speech: SpeechHelper = SpeechHelper(), // my class that converts text to speech
viewModel: VolumeSettingsViewModel = hiltViewModel(), // using Hilt to inject ViewModels
navController: NavHostController = rememberNavController() // Compose Navigation component
) {
MyAppheme {
Box(
...
)
}
}
When I rollbacked some changes I realized that the #Preview does not support the viewModels regardless of whether they are injected with Hilt or not.
Any Idea how this could be fixed?
Have you considered having a structure where you have a Screen and the actual Content separated like this?
// data class
data class AccountData(val accountInfo: Any?)
// composable "Screen", where you define contexts, viewModels, hoisted states, etc
#Composable
fun AccountScreen(viewModel: AccountViewModel = hiltViewModel()) {
val accountData = viewModel.accountDataState.collectAsState()
AccountContent(accountData = accountData) {
// click callback
}
}
//your actual composable that hosts your child composable widget/components
#Composable
fun AccountContent(
accountData: AccountData,
clickCallback: () ->
) {
...
}
where you can have a preview for the Content like this?
#Preview
#Composable
fun AccountContentPreview() {
// create some mock AccountData
val mockData = AccountData(…)
AccountContent(accountData = mockData) {
// I'm not expecting some actual ViewModel calls here, instead I'll just manipulate the mock data
}
}
this way, all components that aren't needed to be configured by the actual content composable are separated, taking you off from headaches configuring a preview.
Just an added note and could be off-topic, I just noticed you have a parameter like this,
speech: SpeechHelper = SpeechHelper()
you might consider utilizing compositionLocalProvider (if needed), that could clean up your parameters.
I managed to visualize the preview of the screen, by wrapping the ViewModels's functions into data classes, like this:
#OptIn(ExperimentalLifecycleComposeApi::class)
#Composable
#ExperimentalFoundationApi
#Preview
fun VolumeSettingsScreen(
modifier: Modifier = Modifier,
speechCallbacks: SpeechCallbacks = SpeechCallbacks(),
navigationCallbacks: NavigationCallbacks = NavigationCallbacks(),
viewModelCallbacks: VolumeSettingsScreenCallbacks = VolumeSettingsScreenCallbacks()
) {
MyAppheme {
Box(
...
)
}
}
I passed not the ViewModel directly in the compose but needed functions in a Data class for example, like this:
data class VolumeSettingsScreenCallbacks(
val uiState: Flow<BaseUiState?> = flowOf(null),
val onValueUpSelected: () -> Boolean = { false },
val onValueDownSelected: () -> Boolean = { false },
val doOnBoarding: (String) -> Unit = {},
val onScreenCloseRequest: (String) -> Unit = {}
)
I made a method that generates those callbacks in the ViewModel, like this:
#HiltViewModel
class VolumeSettingsViewModel #Inject constructor() : BaseViewModel() {
fun createViewModelCallbacks(): VolumeSettingsScreenCallbacks =
VolumeSettingsScreenCallbacks(
uiState = uiState,
onValueUpSelected = ::onValueUpSelected,
onValueDownSelected = ::onValueDownSelected,
doOnBoarding = ::doOnBoarding,
onScreenCloseRequest = ::onScreenCloseRequest
)
....
}
In the NavHost I hoisted the creation of the ViewModel like this:
#Composable
#ExperimentalFoundationApi
fun MyAppNavHost(
speech: SpeechHelper,
navController: NavHostController,
startDestination: String = HOME.route,
): Unit = NavHost(
navController = navController,
startDestination = startDestination,
) {
...
composable(route = Destination.VOLUME_SETTINGS.route) {
hiltViewModel<VolumeSettingsViewModel>().run {
VolumeSettingsScreen(
modifier = keyEventModifier,
speechCallbacks = speech.createCallback() // my function,
navigation callbacks = navController.createCallbacks(), //it is mine extension function
viewModelCallbacks = createViewModelCallbacks()
)
}
}
...
}
It is a bit complicated, but it works :D. I will be glad if there are some comets for improvements.

Jetpack Compose Using Same State Value For All Screens

I want to show circularProgressIndicator while an operation is working such as network call. I want to be able to change the indicator visibility state in all viewmodels and observe that value in NavHost because I want to show a transparant layout which has a circular indicator and prevent user from clicking another fields while network call is still going.
I tried to use baseviewmodel class and mutableStateOf(Boolean) in it but whenever I tried to access viewmodel from navhost it's instance is different than other.
Question is basically how can i create a single global mutableStateOf() object and change it's value from all inside of my viewmodels and observe it as a state inside of NavHost composable to change visibility of circularProgressIndicator?
Note: I am using hilt to get instance of viewmodel -> viewModel: MyViewModel = hiltViewModel()
#Composable
fun NavHost(modifier: Modifier = Modifier) {
val navController = rememberAnimatedNavController()
Box {
AnimatedNavHost(
navController = navController,
startDestination = Screen.EmailAndPassword.route,
modifier = modifier.fillMaxSize()
) {
composable(Screen.EmailAndPassword.route) {
EmailAndPasswordScreen {
navController.navigate(Screen.UserInformation.route) {
/*TODO*/
}
}
}
composable(Screen.UserInformation.route) { UserInformationScreen() }
}
LoadingScreen(isVisible = /* IndicatorState */)
}}
#Composable
fun LoadingScreen(modifier: Modifier = Modifier, isVisible: Boolean) {
if (isVisible){
Box(modifier = modifier.background(Color.Transparent).fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}}
I tried to use baseviewmodel class and mutableStateOf(Boolean) in it but whenever I tried to access viewmodel from navhost it's instance is different than other.
I use Compose-Destinations library and for sharing a ViewModel throughout the Activity, I do it like this (This is a sample from documentation):
#Composable
fun AppNavigation(
activity: Activity
) {
DestinationsNavHost(
//...
dependenciesContainerBuilder = { //this: DependenciesContainerBuilder<*>
// 👇 To tie SettingsViewModel to "settings" nested navigation graph,
// making it available to all screens that belong to it
dependency(NavGraphs.settings) {
val parentEntry = remember(navBackStackEntry) {
navController.getBackStackEntry(NavGraphs.settings.route)
}
hiltViewModel<SettingsViewModel>(parentEntry)
}
// 👇 To tie ActivityViewModel to the activity, making it available to all destinations
dependency(hiltViewModel<ActivityViewModel>(activity))
}
)
}

Pass value of ViewModel to a new Composable screen instance

I have a composable function declared like this:
fun ScreenA(
nav: NavController,
type: SomeTypeObject,
) {
val vm = getViewModel<SomeTypeObjectViewModel>()
val state = rememberScaffoldState()
val scope = rememberCoroutineScope()
LaunchedEffect(LocalContext.current) {
when(type) {
SomeTypeObject.TYPE1 ->{
vm.updateState("1")
}
SomeTypeObject.TYPE2 -> {
//do something else
}
}
}
SomeTypeObjectViewModel contains state variable of my ScreenA like this:
var remeberVal = mutableStateOf<SomeTypeObject?>(null)
Now at some point in another composable function i use my navigationGraph to open another instance of ScreenA, so SomeTypeObjectViewModel gets recreated and remeberVal restes istelf but i want keep and reuse it when new instance of ScreenA is made.
Passing remeberVal as argument using the navigationGraph is not an option since you can only pass Strings, ints or parcelable objects which is not my case, considering that remeberVal has MutableState<SomeTypeObject?> type.
At this point my question is:
Is there a way to pass remeberVal to the new instance of ScreenA or to avoid SomeTypeObjectViewModel being reinstantiated after when i re-route to ScreenA using my navigaion graph?
Thank you!
Edit:
my getViewModel() is a Koin function to injevt the ViewModel, the internal code is:
org.koin.androidx.compose ViewModelComposeExtKt.class #Composable
public inline fun <reified T : ViewModel> getViewModel(
qualifier: Qualifier?,
owner: ViewModelStoreOwner,
scope: Scope,
noinline parameters: ParametersDefinition? /* = (() → ParametersHolder)? */
): T
The navigation graph is made in something like this way:
fun MyNGraph(nav: NavHostController) {
composable(
route = Routes.CaseType1.route + "/{someParameters}/",
arguments = listOf(
navArgument("someParameters") {},
),
) { backStackEntry ->
val someParameters = backStackEntry.arguments?.getString("someParameters")
someParameters?.let { someParameters ->
ScreenA(
type = SomeTypeObject.TYPE1, // Notice here, where i change type but use the same screen
)
}
}
}
composable(
route = Routes.CaseType2.route + "/{someParameters}/",
arguments = listOf(
navArgument("someParameters") {},
),
) { backStackEntry ->
val someParameters = backStackEntry.arguments?.getString("someParameters")
someParameters?.let { someParameters ->
ScreenA(
type = SomeTypeObject.TYPE2, // Notice here
)
}
}
}
}
You are using Koin for DI, so you can just add a dependency with a broader scope than your SomeTypeObjectViewModel that will hold the state you want to share between different screens/composables or between different VM instances. In that way your VMs have access to a shared state (a shared state holder is usually called a Repository).
class MySharedState {
// this could also be a MutableState instead of MutableStateFlow
// but then you are spreading the androidx.compose.runtime dependency
// to a shared state that should not need to know about Compose
val typeFlow = MutableStateFlow<SomeTypeObject?>(null)
}
class SomeTypeObjectViewModel(
val sharedState: MySharedState
): ViewModel() {
fun updateType(type: SomeTypeObject) {
sharedState.typeFlow.value = type
}
fun updateState(value: String) {
// your existing logic...
// call updateType(...) when you want to update the type
}
// rest of your ViewModel code
}
Where you are configuring your Koin modules add (if you are using Koin 3.2+)
module {
// a shared state scoped to the whole app lifecycle
singleOf(::MySharedState) // <-- add this
viewModelOf(::SomeTypeObjectViewModel) // <-- you probably already have this
}
If you are using Koin < 3.2
module {
// a shared state scoped to the whole app lifecycle
single { MySharedState() } // <-- add this
viewModel { SomeTypeObjectViewModel(get()) } // <-- you probably already have this but add one more get()
}
If you also want to access the state in your composables, you can use Flow.collectAsState()
fun ScreenA(
nav: NavController,
type: SomeTypeObject,
) {
val vm = getViewModel<SomeTypeObjectViewModel>()
val currentType by vm.sharedState.typeFlow.collectAsState()
// ...
}
by scoping your ViewModel to navigation routes or the navigation graph you can retrieve the same instance of your ViewModel
visit https://developer.android.com/jetpack/compose/libraries#hilt-navigation
#Composable
fun MyApp() {
NavHost(navController, startDestination = startRoute) {
navigation(startDestination = innerStartRoute, route = "Parent") {
// ...
composable("exampleWithRoute") { backStackEntry ->
val parentEntry = remember(backStackEntry) {
navController.getBackStackEntry("Parent")
}
val parentViewModel = hiltViewModel<ParentViewModel>(parentEntry)
ExampleWithRouteScreen(parentViewModel)
}
}
}
}

When I change ViewModel var, Composable Doesn't Update in Kotlin + Compose

When I change ViewModel variable, Composable Doesn't Update the View and I'm not sure what to do.
This is my MainActivity:
class MainActivity : ComponentActivity() {
companion object {
val TAG: String = MainActivity::class.java.simpleName
}
private val auth by lazy {
Firebase.auth
}
var isAuthorised: MutableState<Boolean> = mutableStateOf(FirebaseAuth.getInstance().currentUser != null)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val user = FirebaseAuth.getInstance().currentUser
setContent {
HeroTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
if (user != null) {
Menu(user)
} else {
AuthTools(auth, isAuthorised)
}
}
}
}
}
}
I have a a View Model:
class ProfileViewModel: ViewModel() {
val firestore = FirebaseFirestore.getInstance()
var profile: Profile? = null
val user = Firebase.auth.currentUser
init {
fetchProfile()
}
fun fetchProfile() {
GlobalScope.async {
getProfile()
}
}
suspend fun getProfile() {
user?.let {
val docRef = firestore.collection("Profiles")
.document(user.uid)
return suspendCoroutine { continuation ->
docRef.get()
.addOnSuccessListener { document ->
if (document != null) {
this.profile = getProfileFromDoc(document)
}
}
.addOnFailureListener { exception ->
continuation.resumeWithException(exception)
}
}
}
}
}
And a Composable View upon user autentication:
#Composable
fun Menu(user: FirebaseUser) {
val context = LocalContext.current
val ProfileVModel = ProfileViewModel()
Column(
modifier = Modifier
.background(color = Color.White)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text("Signed in!");
ProfileVModel.profile?.let {
Text(it.username);
}
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
) {
TextButton(onClick = {
FirebaseAuth.getInstance().signOut()
context.startActivity(Intent(context, MainActivity::class.java))
}) {
Text(
color = Color.Black,
text = "Sign out?",
modifier = Modifier.padding(all = 8.dp)
)
}
}
}
}
When my Firestore method returns, I update the profile var, and "expect" it to be updated in the composable, here:
ProfileVModel.profile?.let {
Text(it.username);
}
However, nothing is changing?
When I was adding firebase functions from inside composable, I could just do:
context.startActivity(Intent(context, MainActivity::class.java))
And it would update the view. However, I'm not quite sure how to do this from inside a ViewModel, since "context" is a Composable-specific feature?
I've tried to look up Live Data, but every tutorial is either too confusing or differs from my code. I'm coming from SwiftUI MVVM so when I update something in a ViewModel, any view that's using the value updates. It doesn't seem to be the case here, any help is appreciated.
Thank you.
Part 1: Obtaining a ViewModel correctly
On the marked line below you are setting your view model to a new ProfileViewModel instance on every recomposition of your Menu composable, which means your view model (and any state tracked by it) will reset on every recomposition. That prevents your view model to act as a view state holder.
#Composable
fun Menu(user: FirebaseUser) {
val context = LocalContext.current
val ProfileVModel = ProfileViewModel() // <-- view model resets on every recomposition
// ...
}
You can fix this by always obtaining your ViewModels from the ViewModelStore. In that way the ViewModel will have the correct owner (correct lifecycle owner) and thus the correct lifecycle.
Compose has a helper for obtaining ViewModels with the viewModel() call.
This is how you would use the call in your code
#Composable
fun Menu(user: FirebaseUser) {
val context = LocalContext.current
val ProfileVModel: ProfileViewModel = viewModel()
// or this way, if you prefer
// val ProfileVModel = viewModel<ProfileViewModel>()
// ...
}
See also ViewModels in Compose that outlines the fundamentals related to ViewModels in Compose.
Note: if you are using a DI (dependency injection) library (such as Hilt, Koin...) then you would use the helpers provided by the DI library to obtain ViewModels.
Part 2: Avoid GlobalScope (unless you know exactly why you need it) and watch out for exceptions
As described in Avoid Global Scope you should avoid using GlobalScope whenever possible. Android ViewModels come with their own coroutine scope accessible through viewModelScope. You should also watch out for exceptions.
Example for your code
class ProfileViewModel: ViewModel() {
// ...
fun fetchProfile() {
// Use .launch instead of .async if you are not using
// the returned Deferred result anyway
viewModelScope.launch {
// handle exceptions
try {
getProfile()
} catch (error: Throwable) {
// TODO: Log the failed attempt and/or notify the user
}
}
}
// make it private, in most cases you want to expose
// non-suspending functions from VMs that then call other
// suspend factions inside the viewModelScope like fetchProfile does
private suspend fun getProfile() {
// ...
}
// ...
}
More coroutine best practices are covered in Best practices for coroutines in Android.
Part 3: Managing state in Compose
Compose tracks state through State<T>. If you want to manage state you can create MutableState<T> instances with mutableStateOf<T>(value: T), where the value parameter is the value you want to initialize the state with.
You could keep the state in your view model like this
// This VM now depends on androidx.compose.runtime.*
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
class ProfileViewModel: ViewModel() {
var profile: Profile? by mutableStateOf(null)
private set
// ...
}
then every time you would change the profile variable, composables that use it in some way (i.e. read it) would recompose.
However, if you don't want your view model ProfileViewModel to depend on the Compose runtime then there are other options to track state changes while not depending on the Compose runtime. From the documentation section Compose and other libraries
Compose comes with extensions for Android's most popular stream-based
solutions. Each of these extensions is provided by a different
artifact:
Flow.collectAsState() doesn't require extra dependencies. (because it is part of kotlinx-coroutines-core)
LiveData.observeAsState() included in the androidx.compose.runtime:runtime-livedata:$composeVersion artifact.
Observable.subscribeAsState() included in the androidx.compose.runtime:runtime-rxjava2:$composeVersion or
> androidx.compose.runtime:runtime-rxjava3:$composeVersion artifact.
These artifacts register as a listener and represent the values as a
State. Whenever a new value is emitted, Compose recomposes those parts
of the UI where that state.value is used.
This means that you could also use a MutableStateFlow<T> to track changes inside the ViewModel and expose it outside your view model as a StateFlow<T>.
// This VM does not depend on androidx.compose.runtime.* anymore
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
class ProfileViewModel : ViewModel() {
private val _profileFlow = MutableStateFlow<Profile?>(null)
val profileFlow = _profileFlow.asStateFlow()
private suspend fun getProfile() {
_profileFlow.value = getProfileFromDoc(document)
}
}
And then use StateFlow<T>.collectAsState() inside your composable to get the State<T> that is needed by Compose.
A general Flow<T> can also be collected as State<T> with Flow<T : R>.collectAsState(initial: R), where the initial value has to be provided.
#Composable
fun Menu(user: FirebaseUser) {
val context = LocalContext.current
val ProfileVModel: ProfileViewModel = viewModel()
val profile by ProfileVModel.profileFlow.collectAsState()
Column(
// ...
) {
// ...
profile?.let {
Text(it.username);
}
// ...
}
}
To learn more about working with state in Compose see the documentation section on Managing State. This is fundamental information to be able to work with state in Compose and trigger recompositions efficiently. It also covers the fundamentals of state hoisting. If you prefer a coding tutorial here is the code lab for State in Jetpack Compose.
An introduction to handling the state as the complexity increases is in the video from Google about Using Jetpack Compose's automatic state observation.
Profile in view model should be State<*>
private val _viewState: MutableState<Profile?> = mutableStateOf(null)
val viewState: State<Profile?> = _viewState
In composable
ProfileVModel.profile.value?.let {
Text(it.username);
}
I recommend using MutableStateFlow.
a simple sample is described in this Medium article :
https://farhan-tanvir.medium.com/stateflow-with-jetpack-compose-7d9c9711c286

Categories

Resources