Jetpack Compose Best& Bad Practices with Common Usages Part I

Süleyman Başaranoğlu
5 min readFeb 22, 2024

In this discussion, we will briefly explore the common usages in Jetpack Compose in three tips.

9 Tips with 3 Part

Tip 1 : Cross-Cutting Concerns

Problem: Over-reliance on CompositionLocal for data transmission can inadvertently tie your UI components too closely to your data layer or business logic.

  • Challenges of Coupling UI and Business Logic
  • Issues with Scalability and Maintainability
  • Challenges in Unit Testing

Solutions: Reserve CompositionLocal for truly global application settings like themes or localizatio n that don’t change often and are universally applicable across your UI. Define a CompositionLocal provide a value at a higher level in your composable tree, and then access that value anywhere in the tree.Or Use ViewModel and State Hoisting.

  • Reduce the Use of CompositionLocal
  • Utilize ViewModel and State Hoisting
  • Provide a value at a higher level

Example: Consider accessed globally analitics event.


Let’s Coding

  1. GoodPracticeHighLevel(), this function illustrates the correct practice by providing an analytics instance using CompositionLocal at a higher level in the composable hierarchy, such as in a main activity, which is akin to the root of the composable tree. This approach allows any composable that requires analytics to access it without being tightly coupled to the analytics implementation.
  2. SomeComposableScreen(),even though LocalAnalytics.current is accessed directly, it is not indicative of tight coupling because the analytics instance is being provided by the higher-level CompositionLocal. This is actually a recommended practice as it aligns with the solution provided in the image, which advises defining a CompositionLocal at a higher level and then accessing that value within the tree.
  3. TightCouplingAnalyticsExampleScreen(),might imply tight coupling, but without seeing the specifics of how analytics is initialized within the composable, it's not possible to ascertain if it's tightly coupled. If analytics were being created inside this composable, it would indeed represent tight coupling. However, if this composable is simply consuming the analytics provided from a higher level via CompositionLocal, then it would not be tightly coupled.
Solution 1

GoodPracticeWithViewModel(), is shown as a good practice where a ViewModel is used to handle the analytics, promoting better separation of concerns and making the UI logic more testable and maintainable.

Solution 2

Tip 2 : Nested Scrolling Strategies

Problem: Using Compose, if items within a LazyColumn (or any scrollable composable) have potentially infinite heights, it can be challenging to create complex layouts, especially when a horizontally scrollable component is followed by a vertically scrollable component. This can lead to performance issues and even application crashes.

Solutions: Setting a fixed height for vertically scrollable components: This can help prevent infinite height scenarios and improve performance by limiting the amount of content that needs to be measured and laid out.

  1. LazyListScope : This seems to suggest creating custom scopes or using existing ones to manage the composition of items in a lazy list more effectively.
  2. Adding items with forEach for small datasets: This is recommended when the dataset is small and performance is not a critical concern, which allows for a simpler implementation.
  3. Setting a fixed height for vertically scrollable components, this can help prevent infinite height scenarios and improve performance by limiting the amount of content that needs to be measured and laid out.

Example: Consider a common home page found in many apps featuring both horizontal and vertical scroll lists.

Nested Scrolling

Let’s Coding

  • BadPractice() ,potentially illustrates a bad practice where a LazyColumn is nested within another LazyColumn, which can lead to performance issues due to the infinite height problem.
  • GoodPractice(), demonstrates a better practice by using a LazyColumn and then adding items using a forEach loop, which would be more performant. It also shows the use of a custom LazyListScope function myLazyColumn(), which might be a tailored solution to manage the items more effectively within the lazy column.
  • myLazyColumn(), to be a custom extension function on LazyListScope that provides a way to define how items should be composed in a LazyColumn, potentially optimizing the layout and scrolling performance.

P.S. While a forEach loop can be used for simplicity with small datasets, LazyListScope should be preferred when dealing with larger datasets or when building complex nested scrollable layouts to ensure smooth performance and avoid crashes due to memory constraints or layout processing overhead.

Solution 3

Tip 3 : Preventing Recomposition Loops

Problem: Directly modifying a state after it has been read within the same Composable can lead to infinite recomposition loops. This happens because Compose attempts to re-read the state and recompose the UI component, triggering an endless cycle of updates.

Solution : It’s crucial not to update a state immediately after reading it within a Composable. State modifications should be handled through event handlers, such as onClick listeners, to decouple state reading from updating.

Example: Imagine maintaining a product count that increments with every user interaction.

Recompose Loop

Let’s Coding

The BadPractice composable function shows a variable count, defined with remember { mutableStateOf(0) }, being directly incremented within the body of the composable function (count++). This modification triggers a recomposition because the state has changed, and since this is done directly in the composable body, it results in an immediate attempt to recompose again, creating a loop.

The GoodPractice composable function correctly handles state modification. The state is still held in a var count defined with remember { mutableStateOf(0) }, but the modification (count++) is performed inside an event handler — the onClick listener of a Button. This decouples the reading of the state from its modification, thereby preventing a recomposition loop. When the button is clicked, the state is updated, which triggers recomposition in a controlled manner, only once per click event.

Solution 4

Happy coding! Check Part II.

For Jetpack Compose Fully Projects

Jetpack Compose with Hexagonal and Clean Architecture

Multi Module( Layer Based)with Clean Architecture & MVVM+MVIÂ

Boost Your Android App with CPU Profiling

Boost Your Android App with and Mastering with Memory Profiling

Enhancing Code Quality Lint with Custom Rules