Introduction to Jetpack compose
Modern, native and declarative UI toolkit for Android
Jetpack compose has been the new topic of discussion recently. Many companies are debating if it is worth having a new toolkit when we already have an existing view system where we declare our layouts in xml, and works perfectly fine. This article is to dissect the question and see the pros and cons of jetpack compose. To start, what is jetpack compose?
What is compose?
Jetpack compose is a modern native declarative UI toolkit for android apps. It is intuitive, powerful, better performance and lets us develop UI with much less code. In other words it is faster and easier. But what was the need to even create a new toolkit when the old view system was doing the trick?
What was the need to develop a new UI toolkit?
View UI toolkit has been existed for around 10years now. Over these years technology has improved, devices are more powerful, UI is more dynamic and expressive and more is expected of apps. Jetpack compose is a modern toolkit based on modern architecture and captalizes on the capabilities of Kotlin. It is faster and has better perforamance. Faster to develop and maintain.
Modern native declarative UI toolkit
We said compose is a modern native declarative UI toolkit. But what does this mean? Let's look at each of these word separately and understand what they mean for compose.
Modern native - Compose is written in Kotlin for android. It has full flexibility of the underlying language (Kotlin). We can use if condition to decide to show a particular UI element or loops for other logic. As compose is written in Kotlin. It has full flexibility to take advantage of the language. This power and flexibility is one of the key advantages of Jetpack Compose.
Declarative - Historically android views are represented hierarchy as a tree of UI widget. Everytime data changes we have observers that observe the data from view model and update the UI with the new data. This approach of manipulating views manually is more prone to errors. It is also possible to create illigal states. Eg: if we try to update a view thats been removed.
compose simplifies engineering associated with building and updating UI. It is immutable unlike the view system. The concept is to generate the entire screen from scrtach and apply only the necessary changes instead of updating the views with new data. In other words we only tell compose what to present, not how to present. This approach avoids the complexity of manually updating a stateful view hierarchy. This is why compose is a declarative UI framework. Every time data changes compose regenerates the UI from scratch. This is called Recomposition.
How do we tell compose what to present? Unlike view system compose does not need observables. It can take the view model as input in a composable function and every time the data changes the UI will be recomposed.
However declaring the entire screen from scratch can be potentially expensive, in terms of time, computing power, and battery usage. To mitigate this cost, Compose intelligently chooses which parts of the UI need to be redrawn at any given time. Developers need to keep in mind some implications we might have while designing the UI components as discussed below in recomposition.
UI toolkit - Jetpack compose components are built to support Material Design principles and theming system.
If you have an exciting app and want to move to compose. Can compose co-exist with view system?
Yes, Compose can co-exist with the present view system. We can adapt it as gradually as we want. We can embed compose within views or views within compose. This can be useful to implements displaying content thats not yet developed using compose like maps and ads.
Recomposition
As discussed above, every time parameter state changes the UI is recomposed with the new state. To do this we use the tag @Composable
above the functions. This tag tells the compose compiler that the function does not have a return type. It takes in the data via paramters and converts it into UI.
One challenge with regenerating the entire screen is that it is potentially expensive, in terms of time, computing power, and battery usage. To mitigate this cost, Compose intelligently chooses which parts of the UI need to be redrawn at any given time. This does have some implications for how you design your UI components as discussed below. I suggest read the documentation to understand these implications better
- Composable functions can execute in any order- When we make a call to number of composible functions they can execute in any order. For this reason functions should be as independent as possible. For example calls to
StartScreen
,MiddleScreen
, andEndScreen
might happen in any order. This means you can't, for example, haveStartScreen()
set some global variable (a side-effect) and haveMiddleScreen()
take advantage of that change. Instead, each of those functions needs to be self-contained.
2. Composable functions can execute in parallel- Compose can optimize recomposition by running composable functions in parallel. This lets Compose take advantage of multiple cores, and run composable functions not on the screen at a lower priority.
3. Recomposition skips as many composable functions and lambdas as possible- When portions of your UI are invalid, Compose does its best to recompose just the portions that need to be updated. This means it may skip to re-run a single Button’s composable without executing any of the composable’s above or below it in the UI tree.
4. Recomposition is optimistic and may be canceled- Recomposition starts whenever Compose thinks that the parameters of a composable might have changed. Recomposition is optimistic, which means Compose expects to finish recomposition before the parameters change again. If a parameter does change before recomposition finishes, Compose might cancel the recomposition and restart it with the new parameter.
5. A composable function might be run quite frequently, as often as every frame of an animation- Composable functions might be re-executed as often as every frame, such as when an animation is being rendered. Composable functions should be fast to avoid jank during animations. If you need to do expensive operations, such as reading from shared preferences, do it in a background coroutine and pass the value result to the composable function as a parameter.Some other actions that can cause a lag in UI are
- Updating shared preferences
- Updating an observable in
ViewModel
- Writing to a property of a shared object
Conclusion:
Jetpack compose is a handy and efficient tool to learn. Although it a bit of a learning curve it will make everyday life easier, less prone to errors and increases app performance. Here is one article that talks about the apk sizes of the app with and without compose.
References:
Thinking in compose — compose paradign and recomposition design guidlines