Compose Basics and Dos and donts

Nabil Mosharraf Hossain
5 min readJul 17, 2023

--

Android introduced Jetpack Compose, a new declarative UI toolkit for building native user interfaces.

Jetpack Compose Basics

1- Composable Functions

XML interfaces can be refactored using Compose, typically resulting in a more concise, streamlined codebase. Here’s a look at a simple composable function:

This function is quick, idempotent, and devoid of side-effects. That means, its behavior remains consistent when invoked multiple times with identical arguments, and it doesn’t depend on external variables or calls to random(). Furthermore, it outlines the UI without inducing any side-effects, like altering properties or global variables.

2- Layouts

Layouts play a vital role in UI development. Jetpack Compose provides an array of layout structures for arranging UI elements. The three basic layout components are Column, Row, and Box:

  • Column: Stacks items from top to bottom
  • Row: Aligns items from left to right
  • Box: Overlays items on top of each other

Here’s an example of a column with two Text composables:

@Composablefun TwoText() {
Column {
Text(text = "Android")
Text(text = "Jetpack Compose")
}}

3- Modifiers

Modifiers, akin to XML attributes, are used for styling your UI. They are, however, much more flexible and offer additional features. Modifiers give you the liberty to tweak or modify the default behavior of a Composable, thereby allowing you to change its appearance, incorporate accessibility information, process UI event interactions, and more.

Text(text = "Your Text", modifier = Modifier.padding(5.dp))

One of the standout features of Modifiers is their ability to provide layering to your Composable without the need for nesting it within other Composables. This would be a challenging task in Android’s traditional UI system, which requires the nesting of multiple Views.

With Modifiers in Compose, though, you can accomplish this with just a single Composable. This is possible as the order in which modifiers are applied is crucial, and by strategically using padding and coloring, you can achieve a wide array of UI designs.

Text(text = "Fake Button",
modifier = Modifier.padding(5.dp)
.background(Color.Magenta)
.padding(5.dp)
.background(Color.Yellow))

4- Lazy Lists/RecyclerView in Compose

LazyLists in Compose are the equivalent to RecyclerViews. Thankfully, the days of writing tedious RecyclerView Adapters, ViewHolders, and their accompanying boilerplate code are over. The following example showcases a LazyList in a Column format (vertical scroll) that displays distinct UI elements based on the integer’s modulus. With a RecyclerView, this would necessitate an Adapter and at least two different ViewHolders. With Compose, however, a LazyColumn Composable with an items function dynamically adds our content is all you need.

@Composablefun ComposeRecyclerView(messages: List<String>) {
LazyColumn {
items(messages) { message ->
Text(message)
}
}}

Thinking in Compose

Jetpack Compose follows a declarative approach, unlike many imperative object-oriented UI toolkits. Widgets are relatively stateless and do not expose setter or getter functions. To update the UI, you call the same composable function with different arguments. Compose encourages developers to minimize state handling within Composables, instead managing it through components such as a ViewModel and observable data structures like LiveData or StateFlow.

In the flow above, the UI layer would be your Composable. Events originating from this layer, such as button clicks are passed to the Event Handler, such as a ViewModel. The ViewModel will provide the UI with State via LiveData/StateFlow. As the State is changed, updates are pushed to your Composables which are then recomposed using the newly updated State. This is what is known as Unidirectional Data Flow.

Composition and Recomposition

Composition is the process in which your Composable functions are executed and the UI is created. Recomposition, on the other hand, is the process of updating the UI as a result of a State or Data Change that a Composable uses for display. During recomposition, Compose understands which data each Composable uses and only updates the UI components that have changed.

Composition/Recomposition should not be equated to a LifeCycle.

  • Composable functions may be recomposed as often as every frame (i.e. animation)
  • Composable functions may be called in any order
  • Composable functions may be executed in parallel

This means that you should never include logic that executes when a Composable function is executed — sometimes referred to as Side-Effects.

@Composable
fun Compose_Theme{
MainScreen()
// DO NOT DO THIS
viewModel.makeAPICall()
}

Stateful Composables!

While Composables should be largely stateless, sometimes we need portions of them to be stateful, for example, to remember a scroll state or share a variable between Composables. The remember and rememberSaveable functions provided by Compose allow for this.


@Composable
fun ColumnWithButton() {
val (buttonCount, setButtonCount) = rememberSaveable { mutableStateOf(0) }
Column {
Button(onClick = { setButtonCount(buttonCount + 1) }) {
Text(text = "Press Me!")
}
Text(text = "Button Pressed $buttonCount times")
}

Compose & Navigation

Jetpack Compose can leverage many of the features that we are accustomed to with Jetpack Navigation. However, you can also just hold the containers as Fragments usually do, and then use the normal Android Navigation of starting an Activity or Fragment.

class ProfileFragment : BaseFragment<Nothing>() {

@ExperimentalMaterialApi
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {

}
}
}
}
}

Do’s and Don’ts in Jetpack Compose:

  • Do use composable functions to describe the UI. Avoid side effects in your Composable functions.
  • Do use layout components (Column, Row, Box) appropriately to arrange your UI elements.
  • Do use Modifiers to change the appearance and behavior of a Composable.
  • Do use LazyList for efficient and simplified lists.
  • Don’t use setter or getter functions for widgets, widgets are relatively stateless in Jetpack Compose.
  • Do manage state using ViewModel and observable data structures like LiveData or StateFlow.
  • Don’t include logic that executes when a Composable function is executed (also known as Side-Effects).
  • Dont use Jetpack Navigation features, instead use normal Android Navigation if your project heavily relies on Fragments.

As Jetpack Compose continues to evolve, it promises to become an increasingly indispensable tool in the Android developer’s toolkit.

Thanks for reading

Links:

https://developer.android.com/jetpack/compose/performance/bestpractices

https://medium.com/captech-corner/jetpack-compose-concepts-every-developer-should-know-5bcb47914542

--

--