Jetpack Compose

Hero image for Jetpack Compose

These guidelines outline the best practices and conventions for building native UI with Jetpack Compose.

Modifiers

Several conventions apply to the Modifiers parameter, as mentioned below:

  • The modifier parameter must be named as “modifier”.
  • The modifier parameter must appear as the first optional parameter in the composable function’s parameter list.

Composable functions must NOT accept multiple Modifier parameters.

@Composable
fun LoginButton(
  modifier: Modifier = Modifier
) {}

State and Event

Following official best practices, use remember if possible to minimize expensive calculations whenever the composable functions are recomposed.

@Composable
fun LoginButton(
  modifier: Modifier = Modifier
) {
  val rememberLoginState = remember { mutableStateOf(loginState) }
  LoginButton(modifier) {
    enabled = rememberLoginState
  }
}
@Composable
fun LoginButton(
  modifier: Modifier = Modifier
) {
  LoginButton(modifier) {
    enabled = mutableStateOf(loginState)
  }
}

Conventions

Naming Unit @Composable functions as entities

The names of composable functions as entities must be a noun, but may be prefixed by descriptive adjectives written in PascalCase.

  • Naming composable functions for screens
@Composable
fun HomeScreen() {}
@Composable
fun homeScreen() {}

  • Naming composable functions for widgets
@Composable
fun LoginButton() {}
@Composable
fun loginButton() {}
@Composable
fun renderLoginButton() {}

  • Naming preview functions with a Preview suffix
@Preview(showBackground = true)
@Composable
fun HomeScreenPreview() {}
@Preview(showBackground = true)
@Composable
fun HomeScreenpreview() {}

Naming @Composable functions

The names of composable functions that return values other than Unit must follow the standard Kotlin Coding Conventions for naming functions written in camelCase.

  • Naming @Composable functions that return values
@Composable
fun defaultStyle(): Style {
@Composable
fun DefaultStyle(): Style {

  • Naming @Composable functions that remember {} the objects they return

The names of composable factory functions that internally remember {} and return a mutable object must be prefixed with remember.

@Composable
fun rememberLoginState(): LoginState = remember {
  LoginState()
}
@Composable
fun createLoginState(): LoginState = remember {
  LoginState()
}

Ordering of annotations and Preview functions

  • @Preview must be placed on top of all other annotations.
  • @Composable must be located on top of class/function definition.
  • Preview functions must be added to the bottom of the files.
@Composable
fun LoginButton() {}

@Composable
fun LogoutButton() {}

...

@Preview(showBackground = true)
...
@Composable
fun LoginButtonPreview() {}

@Preview(showBackground = true)
...
@Composable
fun LogoutButtonPreview() {}

Separating compose functions into different files

There has been no official document on when to separate composable functions into different files yet, but these methods can be followed:

Separation by commonly used

If the component in question is used by multiple composable functions, separate it into a different class file with a generic name and its own @Preview.

@Composable
fun AppToolbar(
  modifier: Modifier = Modifier
) {
  ...
}

@Preview
@Composable
fun AppToolbarPreview() {
  AppToolbar()
}

The composable function should NOT be separated if it is not used by others

@Composable
fun HomeBottomNavigation(navController: NavController) {
  ...
}

@Preview
@Composable
fun HomeBottomNavigationPreview() {
  HomeBottomNavigation(
    navController = rememberNavController()
  )
}

Separation by number of UI states

If the component in question has multiple UI states, separate it into a different class file with its own @Preview.

@Composable
fun CustomPizzaImage(
  pizzaOrder: PizzaOrder,
  modifier: Modifier = Modifier
) {
  with(piazzaOrder) {
    Column(modifier) {
      when (pizzaChoice) {
        PizzaChoice.Empty -> {
          ...
        }
        PizzaChoice.Half -> {
          ...
        }
        PizzaChoice.Full -> {
          ...
        }
      }
      Text(text = pizzaName)
    }
  }
}

@Preview
@Composable
fun CustomPizzaImagePreview(
  @PreviewParameter(PizzaOrderProvider::class) pizzaOrder: PizzaOrder
) {
  CustomPizzaImage(pizzaOrder = pizzaOrder)
}

The composable function should NOT be separated if it does not have multiple UI states.

@Composable
fun OrderItem(
  orderId: String,
  orderCount: Int,
  imageUrl: String,
  modifier: Modifier = Modifier
) {
  ...
}

@Preview
@Composable
fun OrderItemPreview() {
  OrderItem(
    orderId = "1234",
    orderCount = 2,
    imageUrl = ""
  )
}

Detekt Rules

Configuration for Compose

In Jetpack Compose, the team follows the official Android API Guidelines to outline the patterns, best practices, and prescriptive style guidelines for writing idiomatic Jetpack Compose. However, some of these guidelines violate some of the default Detekt rules.

FunctionNaming

Using PascalCase for the function naming that violates the detekt rules:

@Composable
fun HomeScreen() {}
Configuration

Set ignoreAnnotated to [ 'Composable' ].


LongParameterList

In case of having many arguments in functions of Jetpack Compose. For example, the function for the UI widget:

@Composable
private fun HomeScreenContent(
    isFirstItemLoading: Boolean,
    isSecondItemLoading: Boolean,
    firstList: List<FirstItem>,
    secondList: List<SecondItem>,
    onFirstItemClick: (FirstItem) -> Unit = {},
    onSecondItemClick: (SecondItem) -> Unit = {},
    onRefresh: () -> Unit = {}
) {}
Configuration

Set ignoreDefaultParameters to true.


MagicNumber

In case of not specifying the named parameter. For example, when defining the code of color without a parameter name (i.e.,Color(color = 0xFFFFFFFF):

val White = Color(0xFFFFFFFF)
Configuration

Set ignorePropertyDeclaration to true.


TopLevelPropertyNaming

For the Property pattern the team also follows PascalCase from official Android API Guidelines , but for the Constant pattern, it’s still under team discussion.

val White = Color(0xFFFFFFFF)
Configuration

Set the following properties:

  • propertyPattern to '(_)?[A-Za-z][A-Za-z0-9]*'.
  • constantPattern to '[A-Z][_A-Za-z0-9]*'.

UnusedPrivateMember

Detekt will detect the composable preview functions that have been marked with @Preview as unused:

@Preview(showBackground = true)
@Composable
private fun HomeScreenPreview() {
    HomeScreenContent()
}
Configuration

Set the following properties:

  • ignoreAnnotated to [ 'Preview' ].
  • active to true.

References