Choosing between DexGuard and ProGuard in Android development
Discover the nuances between DexGuard and ProGuard – two powerful libraries that can fortify your app against security threats.
Financial services
Expertise in core banking, BaaS integrations, payments, and GenAI-enhanced financial solutions.
Healthcare
People-centric healthcare design and solutions, from virtual care, integrations, to smart devices.
Insurance
Modern solutions including self-service, on-demand, and algorithm-driven personalization.
We’re purposefully shaping the digital future across a range of industries.
Discover some of our specific industry services.
Discover moreFebruary 9, 2023
What are the differences between Jetpack Compose and XML? Read more to find out!
Jetpack Compose is an Android UI toolkit introduced by Google. It improves the way the UI is built in most current Android applications, simplifying the process and speeding it up. One of the best things about it is that it uses Kotlin.
Jetpack Compose, as its name suggests, makes use of the Composite pattern and declarative programming. So, before checking how it works, let’s explain some concepts.
Before talking about composables, we need to know some useful concepts. So, I encourage you to read more about the following topics:
The XML UI system is based on inheritance.
All views inherit from View and all its child views, like a TextView, inherit all the View’s “knowledge”, for example, paint the background. You can think of a TextView as a better version of a View because it adds new functionality.
The same happens with EditText. It inherits from TextView. The “problem” here is that you can only get the knowledge from one specific parent.
Note that an EditText is a TextView and a TextView is a View.
The Compose UI system is based on composition.
A composable can be considered as a View but the relationship is not parent-child like in inheritance. Here we have a composable that can have as many composable references as needed. Each composable can be thought of as a task. If you need a more specialized task, you can nest another composable inside another.
Note that Composable has another composable that has another composable and so on.
Let’s see some code.
Just to compare XML vs Compose let’s build a very simple layout:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns: android= "http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/header"
style="@style/TextAppearance.MaterialComponents.Headline2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:paddingHorizontal="20dp"
android:text="Hello world"
android:textAlignment="center" />
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="content image"
android:paddingHorizontal="20dp"
android:scaleType="centerCrop"
android:layout_marginBottom="20dp"
android:src="@drawable/ic_launcher_foreground" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:backgroundTint="@color/black"
android:textColor="@color/white"
android:text="Upload"
android:textAllCaps="false"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
Same layout with Compose:
val scrollState = remembersScrollState()
val scope = rememberCoroutineScope()
var selectedAnimal by remember { mutableStateOf<Animal?>(null) }
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState),
verticalArrangement = Arrangement.spacedBy(20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Hello world",
style = MaterialTheme.typography.h2,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp)
)
Image(
painter = painterResource(
id = selectedAnimal?.imageSrc ?: R.drawable.ic_launcher_foreground
),
contentDescription = "content image",
modifier = Modifier
.fillMaxWidth(fraction = 0.75f)
.aspectRatio(3 / 8f),
contentScale = ContentScale.Crop
)
Button(
onClick = {
scope.launch {
scrollState.animateScrollTo(0)
}
},
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.Black,
contentColor = Color.White,
)
) {
Text(text = "Upload")
}
}
In compose, functions are used to implement component inheritance. A component function can only be called in another composable function. More atomic components mean a more flexible components structure (but more components to maintain).
In the composable code, you might notice some strange “state” code. Let’s talk a little bit about it.
First of all, what is the state in an app? The state in an app is any value that can change over time. The State determines what is shown in the UI at a particular time.
If we create a composable like this:
@Composable
fun Greetings() {
var name = "world"
Text(
text = "Hello $name"
)
}
We are creating a static composable that prints “Hello world” and we have no chance of updating it.
So, how can we change a composable state? The answer is: events.
Events are inputs generated from outside or inside our application. For example, clicking on a button in the app or receiving a response from a network request.
In all Android apps, there is an UI update loop. This is how it looks:
At first, an initial state is displayed on the screen. If an event happens, the event handler changes the state and now the UI displays the new state on the screen, and so on.
If we modify our current Composable to be something like this:
@Composable
fun Greetings() {
Column(modifier = Modifier.padding(16.dp)) {
var name = "world"
Text(
text = "Hello $name"
)
Button(onClick = {name = “user”}, modifier = Modifier.padding(top = 8.dp)
) {
Text(“Update text”)
}
}
}
After pressing the button you will notice that the UI is not updating. So why? Now the answer is recomposition.
Basically, when the state in a Composable has changed, it has to be re-run to show the new state on the screen.
Compose is able to track the Composable state and schedule recompositions when the state has changed. It’s important to say that only the affected composables will be re-rendered and not the whole UI. To know more about recomposition please refer to the following documentation: Lifecycle of composables
So, we have to use the Compose API to achieve this behavior. Let’s modify our current composable:
@Composable
fun Greetings() {
Column(modifier = Modifier.padding(16.dp)) {
var name: MutableState<String> = remember { mutableStateOf("world") }
Text(
text "Hello ${name.value}"
)
Button(onClick = {name.value = “user”}, modifier = Modifier.padding(top = 8.dp)){
Text(“Update text”)
}
}
}
Here we can see some particular Compose stuff:
Usually MutableState and remember are used together in composable functions.
Using delegates could improve code readability and make it much simpler:
@Composable
fun Greetings() {
Column(modifier = Modifier.padding(16.dp)) {
var name by remember { mutableStateOf("world") }
Text(
text = "Hello $name"
)
Button(onClick = {name = “user”}, modifier = Modifier.padding(top = 8.dp)){
Text(“Update text”)
}
}
}
Before talking about state hoisting, let’s mention what stateful and stateless composables are.
Stateful composable: is a composable that holds its own state
Stateless composable: is a composable that doesn’t hold any state
With that in mind, let’s talk about state hoisting.
State hoisting in Compose, is a pattern of moving state to a composable’s caller to make a composable stateless. It has some important benefits:
To see it more clearly, let’s refactor our previous composable into a stateful and stateless widget:
@Composable
fun StatefulGreetings() {
var name by remember { mutableStateOf("world") }
StatelessGreetings(text = name, onClicked = { name = “user” })
}
@Composable
fun StatelessGreetings(text: String, onClicked: () -> Unit) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello $text"
)
Button(onClick = onClicked, modifier = Modifier.padding(top = 8.dp)) {
Text(“Update text”)
}
}
}
To show items in a list with XML, we need a few files and/or classes:
It is more complicated than it should be. A lot of code to show a simple items list.
We only need a LazyColumn. Just that. In the item/items scope we can specify the item composable. Look at the example:
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 24.dp),
verticalArrangement = Arrangement.spacedBy(20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
item {
Text(
text = "List title",
style = MaterialTheme.typography.h1,
textalign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
// Assume that getListItems() returns a list of objects with a text property
items(getListItems()) { item ->
Text(text = item.text)
}
}
There are a lot of things to take into account when building an items list like recomposition, keys, paging, etc. But since this is an introductory blog post I recommend visiting the following link: Lists and grids
In Compose, everything is composable. And Navigation is not an exception. To perform a navigation, every screen should have a unique route (composable path) and, we will also need a NavHost. Let’s check how to build it.
Suppose that we have two screens in our app: Home and Profile. And we want to navigate from Home to Profile. It is as simple as creating both screens and building a NavHost with the required routes. In this case both composables will be created and navigated in the MainActivity.
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = Navigator.getStartDestination()
) {
composable(Navigator.Home.route) { HomeScreen(navController) }
composable(Navigator.Profile.route) { ProfileScreen(navController) }
}
}
sealed class Navigator(val route: String) {
object Home : Navigator("home")
object Profile : Navigator("profile")
companion object {
fun getStartDestination() = Home.route
}
}
@Composable
fun HomeScreen(
navController: NavController = rememberNavController()
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Home")
Button(
onClick = { navController.navigate(Navigator.Profile.route) },
) {
Text(text = "Go to Profile screen")
}
}
}
@Composable
fun ProfileScreen(
navController: NavController = rememberNavController()
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Profile")
Button(
onClick = { navController.navigateUp() },
) {
Text(text = "Back")
}
}
}
Note that the navController is created in the navigator and passed to the required screens. This follows the principles of state hoisting and allows you to use the NavController and the state it provides via currentBackStackEntryAsState() to be used as the source of truth for updating composables outside of your screens. An example of the usage of this functionality could be the usage of a BottomNavigation: Navigating with Compose
This is the official pathway for Jetpack Compose. It includes articles, videos and codelabs.
Try it out: Jetpack Compose | Android Developers
The common practice for building a UI has been Imperative programming. It’s a robust practice but it becomes quite complex when an app is too big and has too many UI elements. Declarative UI, has shown in other frameworks like React and Flutter, that the adoption and implementation is much easier and the development performance increases.
Specifically in Android, Compose has been a complete redesign of the UI system. It has some advantages over traditional XML system like:
If you have been working with XML views until now, it could be that it will require some time to get used to the technology, but its learning curve is not hard at all, so don’t worry!
Get hands on with Compose and see you in the next blog post! And in the meantime, learn more about the work we do in the Qubika App Solutions Studio!
Jetpack Compose | Android Developers
Why Compose | Jetpack Compose | Android Developers
Thinking in Compose | Jetpack Compose | Android Developers
State and Jetpack Compose | Android Developers
Lifecycle of composables | Jetpack Compose | Android Developers
Lists and grids | Jetpack Compose | Android Developers
Navigating with Compose | Jetpack Compose | Android Developers
Mobile Developer
Receive regular updates about our latest work
Discover the nuances between DexGuard and ProGuard – two powerful libraries that can fortify your app against security threats.
Check out what is new in Kotlin, a widely used programming language for Android.
Check out some tips on how to save the UI state during Android development.
Receive regular updates about our latest work
Get in touch with our experts to review your idea or product, and discuss options for the best approach
Get in touchProduct Design Solutions
Artificial Intelligence Services
Healthcare Solutions
Data
App Solutions
Platform engineering
Cybersecurity
SRE & Cloud Services
Quality Assurance
Blockchain
Firmware & IoT Development
Product Management
Financial Services Technology
Insurance