0% found this document useful (0 votes)
21 views5 pages

JetpackCompose1.5EssentialsPreview - Optimized 139 143

Uploaded by

yosfsoliman151
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views5 pages

JetpackCompose1.5EssentialsPreview - Optimized 139 143

Uploaded by

yosfsoliman151
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 5

Chapter 45

45. Working with ViewModels in


Compose
Until a few years ago, Google did not recommend a specific approach to building Android apps other than to
provide tools and development kits while letting developers decide what worked best for a particular project
or individual programming style. That changed in 2017 with the introduction of the Android Architecture
Components which became part of Android Jetpack when it was released in 2018. Jetpack has of course, since
been expanded with the addition of Compose.
This chapter will provide an overview of the concepts of Jetpack, Android app architecture recommendations,
and the ViewModel component.

45.1 What is Android Jetpack?


Android Jetpack consists of Android Studio, the Android Architecture Components, Android Support Library,
and the Compose framework together with a set of guidelines that recommend how an Android App should be
structured. The Android Architecture Components were designed to make it quicker and easier both to perform
common tasks when developing Android apps while also conforming to the key principle of the architectural
guidelines. While many of these components have been superseded by features built into Compose, the
ViewModel architecture component remains relevant today. Before exploring the ViewModel component, it
first helps to understand both the old and new approaches to Android app architecture.

45.2 The “old” architecture


In the chapter entitled “An Example Compose Project”, an Android project was created consisting of a single
activity that contained all of the code for presenting and managing the user interface together with the back-end
logic of the app. Up until the introduction of Jetpack, the most common architecture followed this paradigm
with apps consisting of multiple activities (one for each screen within the app) with each activity class to some
degree mixing user interface and back-end code.
This approach led to a range of problems related to the lifecycle of an app (for example an activity is destroyed
and recreated each time the user rotates the device leading to the loss of any app data that had not been saved
to some form of persistent storage) as well as issues such as inefficient navigation involving launching a new
activity for each app screen accessed by the user.

45.3 Modern Android architecture


At the most basic level, Google now advocates single activity apps where different screens are loaded as content
within the same activity.
Modern architecture guidelines also recommend separating different areas of responsibility within an app into
entirely separate modules (a concept called “separation of concerns”). One of the keys to this approach is the
ViewModel component.

45.4 The ViewModel component


The purpose of ViewModel is to separate the user interface-related data model and logic of an app from the
code responsible for displaying and managing the user interface and interacting with the operating system.
403
Working with ViewModels in Compose
When designed in this way, an app will consist of one or more UI Controllers, such as an activity, together with
ViewModel instances responsible for handling the data needed by those controllers.
A ViewModel is implemented as a separate class and contains state values containing the model data and
functions that can be called to manage that data. The activity containing the user interface observes the model
state values such that any value changes trigger a recomposition. User interface events relating to the model data
such as a button click are configured to call the appropriate function within the ViewModel. This is, in fact, a
direct implementation of the unidirectional data flow concept described in the chapter entitled “An Overview of
Compose State and Recomposition”. The diagram in Figure 45-1 illustrates this concept as it relates to activities
and ViewModels:

Figure 45-1
This separation of responsibility addresses the issues relating to the lifecycle of activities. Regardless of how
many times an activity is recreated during the lifecycle of an app, the ViewModel instances remain in memory
thereby maintaining data consistency. A ViewModel used by an activity, for example, will remain in memory
until the activity finishes which, in the single activity app, is not until the app exits.
In addition to using ViewModels, the code responsible for gathering data from data sources such as web services
or databases should be built into a separate repository module instead of being bundled with the view model.
This topic will be covered in detail beginning with the chapter entitled “Room Databases and Compose”.

45.5 ViewModel implementation using state


The main purpose of a ViewModel is to store data that can be observed by the user interface of an activity. This
allows the user interface to react when changes occur to the ViewModel data. There are two ways to declare the
data within a ViewModel so that it is observable. One option is to use the Compose state mechanism which has
been used extensively throughout this book. An alternative approach is to use the Jetpack LiveData component,
a topic that will be covered later in this chapter.
Much like the state declared within composables, ViewModel state is declared using the mutableStateOf group
of functions. The following ViewModel declaration, for example, declares a state containing an integer count
value with an initial value of 0:
class MyViewModel : ViewModel() {

var customerCount by mutableStateOf(0)

}
With some data encapsulated in the model, the next step is to add a function that can be called from within the
UI to change the counter value:

404
Working with ViewModels in Compose
class MyViewModel : ViewModel() {

var customerCount by mutableStateOf(0)

fun increaseCount() {
customerCount++
}
}

Even complex models are nothing more than a continuation of these two basic state and function building
blocks.

45.6 Connecting a ViewModel state to an activity


A ViewModel is of little use unless it can be used within the composables that make up the app user interface.
All this requires is to pass an instance of the ViewModel as a parameter to a composable from which the state
values and functions can be accessed. Programming convention recommends that these steps be performed in a
composable dedicated solely for this task and located at the top of the screen’s composable hierarchy. The model
state and event handler functions can then be passed to child composables as necessary. The following code
shows an example of how a ViewModel might be accessed from within an activity:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ViewModelWorkTheme {
Surface(color = MaterialTheme.colorScheme.background) {
TopLevel()
}
}
}
}
}

@Composable
fun TopLevel(model: MyViewModel = viewModel()) {
MainScreen(model.customerCount) { model.increaseCount() }
}

@Composable
fun MainScreen(count: Int, addCount: () -> Unit = {}) {
Column(horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()) {
Text("Total customers = $count",
Modifier.padding(10.dp))
Button(
onClick = addCount,
) {
Text(text = "Add a Customer")
405
Working with ViewModels in Compose
}
}
}

In the above example, the first function call is made by the onCreate() method to the TopLevel composable
which is declared with a default ViewModel parameter initialized via a call to the viewModel() function:
@Composable
fun TopLevel(model: MyViewModel = viewModel()) {
.
.

The viewModel() function is provided by the Compose view model lifecycle library which needs to be added to
the project’s build dependencies when working with view models as follows:
dependencies {
.
.
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
.
.

If an instance of the view model has already been created within the current scope, the viewModel() function will
return a reference to that instance. Otherwise, a new view model instance will be created and returned.
With access to the ViewModel instance, the TopLevel function is then able to obtain references to the view model
customerCount state variable and increaseCount() function which it passes to the MainScreen composable:
MainScreen(model.customerCount) { model.increaseCount() }

As implemented, Button clicks will result in calls to the view model increaseCount() function which, in turn,
increments the customerCount state. This change in state triggers a recomposition of the user interface, resulting
in the new customer count value appearing in the Text composable.
The use of state and view models will be demonstrated in the chapter entitled “A Compose ViewModel Tutorial”.

45.7 ViewModel implementation using LiveData


The Jetpack LiveData component predates the introduction of Compose and can be used as a wrapper around
data values within a view model. Once contained in a LiveData instance, those variables become observable to
composables within an activity. LiveData instances can be declared as being mutable using the MutableLiveData
class, allowing the ViewModel functions to make changes to the underlying data value. An example view model
designed to store a customer name could, for example, be implemented as follows using MutableLiveData
instead of state:
class MyViewModel : ViewModel() {

var customerName: MutableLiveData<String> = MutableLiveData("")

fun setName(name: String) {


customerName.value = name
}
}

Note that new values must be assigned to the live data variable via the value property.

406
Working with ViewModels in Compose

45.8 Observing ViewModel LiveData within an activity


As with state, the first step when working with LiveData is to obtain an instance of the view model within an
initialization composable:
@Composable
fun TopLevel(model: MyViewModel = viewModel()) {

Once we have access to a view model instance, the next step is to make the live data observable. This is achieved
by calling the observeAsState() method on the live data object:
@Composable
fun TopLevel(model: MyViewModel = viewModel()) {
var customerName: String by model.customerName.observeAsState("")
}

In the above code, the observeAsState() call converts the live data value into a state instance and assigns it to
the customerName variable. Once converted, the state will behave in the same way as any other state object,
including triggering recompositions whenever the underlying value changes.
The use of LiveData and view models will be demonstrated in the chapter entitled “A Compose Room Database
and Repository Tutorial”.

45.9 Summary
Until recently, Google has tended not to recommend any particular approach to structuring an Android app.
That changed with the introduction of Android Jetpack which consists of a set of tools, components, libraries,
and architecture guidelines. These architectural guidelines recommend that an app project be divided into
separate modules, each being responsible for a particular area of functionality, otherwise known as “separation
of concerns”. In particular, the guidelines recommend separating the view data model of an app from the code
responsible for handling the user interface. This is achieved using the ViewModel component. In this chapter,
we have covered ViewModel-based architecture and demonstrated how this is implemented when developing
with Compose. We have also explored how to observe and access view model data from within an activity using
both state and LiveData.

407

You might also like