In the new era, declarative coding is proven much more efficient and productive than imperative coding.
This method helps user to reduce line of code as well as to improve debugging efficiency. It also improves the runtime performance.
In Halodoc, we are experimenting to migrate from imperative to declarative UI using Jetpack Compose library. It is also feasible to partially adopt Jetpack Compose over small UIs without hampering other components. That we will learn further in blog.
Jetpack Compose aims to simplify and accelerate UI development for Android apps by allowing programmers to define their UIs using a declarative style.
Jetpack compose is fairly rich topic and to get comfortable with that it is strongly recommended to visit Jetpack Compose Pathway. This article aims give you an overview of this feature in a nutshell to aid in your rampup journey.
So to begin with compose first let us ask 3 main WH questions which are What, Why, How.
What : What is Jetpack Compose?
So as mentioned it is part of Declarative UI paradigm. That means UI components does not expose the separate model for UI components, it decides UI population based on What data should be shown rather than concentrating on how it will be shown. So to understand difference between Imperative (Mainstream UI paradigm adopted by android eco system) and Declarative UI please refer this article.
Why : Why we should go for Jetpack Compose?
Now as we know that in traditional Imperative pattern widgets gets exposed as separate component that end up performing heavy computation on data change, this process can be considered as Recomposition.
Where as in declarative pattern data part behaves individually to update UI and only updates necessary part instead of reloading whole UI widget as shown in above image.
So by this way it optimises the processing time significantly and provides better performance.
In addition, as per documentation we should prevent external operations such as fetching data from other sources in UI section which motivates us to separate data operations, consume only the UI-specific data and also prevents memory leak issues.
So apart from this declarative UI benefits Jetpack Compose has additional feature that it can be plugged into the existing code at micro level.
So until now we might got to know the purpose of using Jetpack Compose so let's get into the Implementation.
How : How can we use Jetpack Compose?
- We can make a project or specific module from scratch with use of Jetpack Compose
- We can migrate base level UI to Jetpack Compose and follow bottom up approach to make hybrid app with Imperative and Declarative UI
Make project with Jetpack Compose using Android Studio Arctic Fox.
To get a hassle-free experience, please refer this official document provided by Google.
Create new project by selecting Empty Compose Activity template and it will include necessary file structure and dependencies.
- In this you can observe that material design elements such as Colour, Shape, Theme and Type (for typography) will be added by default in form of kotlin file under ui.theme package which we will be using in compose UI components.
- Following in application level build.gradle you will find following dependencies.
- Further more in build.gradle make sure to remove kotlinCompilerVersion in android -> composeOptions because it is deprecated and compose can use compiler version from build script which mentioned in project level build.gradle.
Create ComponentActivity and add sample UI section inside setContent method as mentioned in below code pen. Remember we need to annotate our function with @Composable annotation to take UI into action.
In above code base we are using JetpackComposeDemoTheme which you can find in Theme.kt file in ui.theme package. This function can provide default theme to UI elements and using setContent method we can specify the root view of an Activity.
In above code base we are defining compose UI elements and the resulting output for this code base will look like below. Here we can mainly use core foundation components such as Row & Column (Similar to Table layout), Surface and Space (to determine spacings) and Text (similar to TextView) and Material components such as Card and SnackBars.
These components are introduced with certain properties like modifier and style based on the utilities so more attributes can be explored by class definition. And this components are declared same as we create object with arguments in constructor.
To understand retrieval of font and styles need to refer this article.
We can fetch Colors from ui.theme -> Color.kt , ShapeDrawables from ui.theme -> Shape.kt, Styles from ui.theme -> Theme.kt and Font from ui.theme -> Type.kt
We can preview the current UI inside android studio without running the application using @Preview annotation. To perform that we need to call our composable function inside preview method as shown in above code base
Migrate existing project to Jetpack Compose.
In this method we will convert existing project to Jetpack Compose. But we need to follow first 2 steps from above section. Plus it is highly recommended to get handy with basic integration by following first method then we can perform migration in existing project in 2 ways.
- Convert each screen to Compose
In this method we will target a Single Activity Architecture and much efficient when Navigation is integrated. Or we can convert activities and fragments into widget as well to migrate in jetpack compose.
To support this we will require Navigation for Compose. It will provide facility to specify single screen with individual widget and navigating between each other.
This method is suitable for small scaled project where changes can be taken place at one go.
- Convert base level UI to Compose
Since Halodoc having multiple applications on large scale it is critical to migrate whole project at one go. So we decided to follow bottom up approach where we target to cover base level UI and then end up changing whole screen.
Here we can add our custom widgets into the xml ViewGroups using setContent method. Please refer this document for further information.
As shown in above image we can integrate greeting widget into the existing xml using ComposeView. Which provides interoperability between imperative and declarative pattern.
There are certain requirements that we came across are
- Jetpack compose requires minimum Android Gradle Plugin version 7.0.0 and gradle-wrapper version to 7.0.2. Which will require certain dependencies to be upgraded.
- In addition kotlin-synthetics and butterknife libraries are deprecated in latest android versions so developers need to migrate to view binding.
- Jetpack compose requires jdk 11 to compile which also affect some of CI/CD pipeline operations. Such as in halodoc we were required to update jacoco task.
- Downloadable fonts are yet not supported by Jetpack Compose.
So to overcome these challenges in Halodoc, we picked one of the projects as it was having latest code with fully kotlin integration and wasn't having much use of butterknife or kotlin synthesis.
In order to migrate in Jetpack Compose at organization level, at Halodoc android team was allocated individual features (such as Api integration with state and livedata, LazyList, Animation and Navigation) to work upon and accommodate into existing halodoc-midwife project. Which leads developers to get handy experience with Compose as well as smooth migration.
- Improve debugging though in imperative pattern developers can't get access to debug UI portion but in Jetpack Compose developers are accessible to all the UI components.
- All UI components works as a class so they are totally customisable as well as reusable.
- With use of recomposition changes only affects to the targeted component instead of rebuilding whole UI tree as in imperative ui patten. Which improves UI performance.
- Lazy List is a good replacement for RecyclerView. In LazyList multiple UI components can be accommodated in single list using widget whereas in RecyclerView user needs to use additional operations such as DiffUtil and manage UI updates using notifyItemChange for UI optimization which becomes critical for complex UIs.
In order to build generation and build size if we are considering the process of partial migration then there will be few disadvantages.
In hybrid approach build generation process acquired more time than complete native integration, and build size increases at some amount.
To test application developer can use following command.
./gradlew --profile --offline --rerun-tasks --max-workers=4 assembleDebug
and to know more about the performance please refer this link with TiVi and Sunflower application example.
Apart from these all Jetpack Compose community is always open to be need full you can reach out to these links.
With the adoption of Declarative UI offered by Jetpack Compose, we've been able to achieve quicker and efficient development cycles, better performance and also faster goto-market for our Android applications and we've significantly benefited through this adoption.
Scalability, reliability and maintainability are the three pillars that govern what we build at Halodoc Tech. We are actively looking for engineers at all levels and if solving hard problems with challenging requirements is your forte, please reach out to us with your resumé at firstname.lastname@example.org.
Halodoc is the number 1 all around Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 3500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allow patients to book a doctor appointment inside our application. We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, Astra, Temasek and many more. We recently closed our Series C round and In total have raised around USD$180 million for our mission. Our team works tirelessly to make sure that we create the best healthcare solution personalised for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.