Visualizing updated data in MVVM with JetpackCompose

Ishraq Shabab
4 min readSep 22, 2024

--

The cornerstone of Android architecture is still the MVVM pattern and Jetpack compose is one of the most used framework in Kotlin.

It is crucial, to have an overview of the architecture of the application and the state of events.

This helps understand thoroughly, how data binding and how live data is being shared amongst the view model and the view.

MutableSateFlow is a Kotlin Flow API used in order to share LiveData and onSave function is being used as well.

In this example, we’re using one viewModel for multiple views. To note that the user can go to the 2 views (intro, services) after landing on the Maid view. The events are being displayed with class and sequential diagrams.

MVVM Class Diagram for Maid

Link between Fragment and View with the ViewModel

Links between Fragment and View

The Fragment is the Android Entry Point and has the viewModel as an attribute and has a Composable which contains the View. There are 3 functionalities that is being passed down from the Fragment to the View are the switch of screens (onBackClicked, onOpenIntro, onOpenServices). In this fragment, the viewModel is being passed down to the view.

@AndroidEntryPoint
class MaidFragment : ComposableFragment () {

private val viewModel: MaidViewModel. by activityViewModels()

@Composable
override fun FragmentComposable() {

MaidView(
viewModel = viewModel,
onBackClicked = {
findNavController.popBackStack()
},
onOpenIntro = {
findNavController().navigate(directions = action)
},
onOpenServices: {
findNavController().navigate(directions = action)
}
)
}
}

Link between View with ViewModel

The MaidView uses the viewModel in order to fetch the latest maid in the application by calling the function .collectAsState().

@Composable
fun MaidView(
viewModel: MaidViewModel,
onBackClicked: () -> Unit = {},
onOpenIntro: (Maid) -> Unit = {},
onOpenServices: (Maid) -> Unit = {},
) {
val maid by remember {
viewModel.maid
}.collectAsState()

maid?.let {
MaidScreen(
maid = it,
onBackClicked = onBackClicked,
onOpenIntro = onOpenIntro,
onOpenServices = onOpenServices,
)
}
}

Define the object (Maid) as a MutableStateFlow <Maid?> and defines the value maid as a SateFlow<Maid?> that takes the MutableStateFlow’s value as a default value.

sequence diagram when the user goes to change the maid’s intro

The viewModel uses the model Maid in order to fetch the latest object which is then being fetch by the view.

@HiltViewModel
class MaidViewModel @Inject constructor() : ViewModel() {
private val _maid: MutableStateFlow<Maid?> = MutableStateFlow(value = null)
val maid: StateFlow<Maid?> = _peerSupport

private val _update: MutableStateFlow<Resource<Any>?> = MutableStateFlow(value = null)
val update: StateFlow<Resource<Any>?> = _update

init {
setPeerSupportInfo()
}

private fun setMaidInfo() {
viewModelScope.launch {
userFlow.filterNotNull().collectLatest {
if (it is Maid) {
_maid.value = it
this.cancel()
}
}
}
}

The 3 other functionalities don’t use the view model, but they passed down the maid object. onOpenIntro redirects to UpdateIntroFragment composable.

@Composable
fun MaidScreen(
modifier: Modifier = Modifier,
maid: Maid,
onBackClicked: () -> Unit = {},
onOpenIntro: (Maid) -> Unit = {},
onOpenServices: (Maid) -> Unit = {},
) {
...
backClicked = onOpenBiography
....
input = {onOpenIntro(maid)}
...
input = {onOpenServices(maid)}
}

Saving the data from the view to the viewModel

Another entry point is being created for the new fragment, the same view model is being passed down and it’s functions are being used to save the data and the sate.

Class Diagram between the Fragments and the Views.

We can observer that both fragments and views share the same view model.

@AndroidEntryPoint
class UpdateIntroFragment : ComposableFragment() {
private val viewModel: MaidViewModel by activityViewModels()

@Composable
override fun FragmentComposable() {

UpdateIntroView(
viewModel = viewModel,
onBackClicked = {
findNavController().popBackStack()
},
onSaveClicked = { intro->
viewModel.updateintro(intro = intro)
},
)
}
}

The Screen calls the function onSave which takes into parameter the intro as a String.

@Composable
fun UpdateIntroView(
viewModel: MaidViewModel,
onBackClicked: () -> Unit = {},
onSaveClicked: (String) -> Unit = { _ -> },
) {
...

maid?.let {
UpdateIntroScreen(
maid = it,
onBackClicked = onBackClicked,
onSaveClicked = onSaveClicked,
)
}
}

@Composable
fun UpdateIntroScreen(
modifier: Modifier = Modifier,
maid: Maid,
onBackClicked: () -> Unit = {},
onSaveClicked: (String) -> Unit = { _ -> },
) {
var intro by remember { mutableStateOf(value = maid.intro) }

...

onSaveClicked(intro)

If we go back the Fragment, we can see that the view model is calling it’s own function updateIntro which takes into parameter the intro from the Screen.

onSaveClicked = { intro->
viewModel.updateintro(intro = intro)
},
The function onSaveClicked(intro) from the UpdateIntroFragment

MaidViewModel.kt

fun updateIntro(intro: String) {
val maid = _maid.value
if (maid != null) {
if (maid.intro != intro) {
val updatedMaid = maid.copyWith(maid = maid)
viewModelScope.launch {
val result = userRepository.updateMaid(maid = updatedMaid)
_updateStatus.value = result
if (result is Resource.Success) {
_maid.value = result.value
userPreferences.updateCurrentMaid(maidDto = maid.value)
}
}
} else {
_updateStatus.value = Resource.Success(value = maid)
}
}
}

The view model is updating the intro in the maid model and then copying its latest object. Once the maid has been updated, its being saved in the repository.

Conclusion

Jetpack compose is a versatile tool used in Kotlin and it can be used a framework to understand the different complexities of MVVM.

--

--