Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Mastering Kotlin for Android 14

You're reading from   Mastering Kotlin for Android 14 Build powerful Android apps from scratch using Jetpack libraries and Jetpack Compose

Arrow left icon
Product type Paperback
Published in Apr 2024
Publisher Packt
ISBN-13 9781837631711
Length 370 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Harun Wangereka Harun Wangereka
Author Profile Icon Harun Wangereka
Harun Wangereka
Arrow right icon
View More author details
Toc

Table of Contents (22) Chapters Close

Preface 1. Part 1: Building Your App
2. Chapter 1: Get Started with Kotlin Android Development FREE CHAPTER 3. Chapter 2: Creating Your First Android App 4. Chapter 3: Jetpack Compose Layout Basics 5. Chapter 4: Design with Material Design 3 6. Part 2: Using Advanced Features
7. Chapter 5: Architect Your App 8. Chapter 6: Network Calls with Kotlin Coroutines 9. Chapter 7: Navigating within Your App 10. Chapter 8: Persisting Data Locally and Doing Background Work 11. Chapter 9: Runtime Permissions 12. Part 3: Code Analysis and Tests
13. Chapter 10: Debugging Your App 14. Chapter 11: Enhancing Code Quality 15. Chapter 12: Testing Your App 16. Part 4: Publishing Your App
17. Chapter 13: Publishing Your App 18. Chapter 14: Continuous Integration and Continuous Deployment 19. Chapter 15: Improving Your App 20. Index 21. Other Books You May Enjoy

Introduction to Jetpack Compose

Over the years, Android UI development has undergone significant transformations with various frameworks and libraries emerging to simplify the process.

Before Jetpack Compose, this is how we used to write UIs for our apps:

  • Views were inflated from XML layout files. XML-based views are still supported alongside Jetpack Compose for backward compatibility and mixed use cases where apps have both XML layouts and Jetpack Compose.
  • Themes, styles, and value resources were also defined in XML files.
  • For us to be able to access the views from XML files, we used view binding or data binding.
  • This method of writing a UI required huge effort, requiring more boilerplate code and being error prone.

Google developed Jetpack Compose as a modern declarative UI toolkit. It allows us to create UIs with less code. Layouts created in Jetpack Compose are responsive to different screen sizes and orientations. It is also easier and more productive to write UIs in Compose. With Jetpack Compose, we can reuse components across our code bases. Jetpack Compose also allows us to use code from XML components in our composables.

Jetpack Compose is entirely in Kotlin, meaning it takes advantage of the powerful language features that Kotlin offers. The view system, which was used to create UIs before Compose, was more procedural. We had to manage complex life cycles and handle any changes in state manually. Jetpack Compose is a whole other paradigm that uses declarative programming. We describe what the UI should be like based on a state. This enables us to have dynamic content and less boilerplate code and develop our UIs faster.

To understand Jetpack Compose, let us first dive deep into the differences between the declarative and imperative approaches to writing UIs.

Declarative versus imperative UIs

In imperative UIs, we specify step by step the instructions describing how the UI should be built and updated. We explicitly define the sequence of operations to create and modify UI elements. We rely on mutable state variables to represent the current state of the UI. We manually update these state variables as the UI changes and respond to user interactions.

In declarative UIs, we focus on describing the desired outcome rather than specifying the step-by-step instructions. We define what the UI should look like based on the current state, and the framework handles the rest. We define the UI using declarative markup or code. We express the desired UI structure, layout, and behavior by describing the relationships between UI elements and their properties.

The declarative approach puts more emphasis on the immutable state, where the UI state is represented by immutable data objects. Instead of directly mutating the state, we create new instances of the data objects to reflect the desired changes in the UI.

In a declarative UI, the framework takes care of updating the UI based on changes in the application state. We specify the relationships between the UI and the underlying state, and the framework automatically updates the UI to reflect those changes.

Now that we understand both imperative and declarative approaches, let’s look at an example of each. Let’s create a simple UI for a counter using both the declarative UI in Jetpack Compose (Kotlin) and the imperative UI in XML (Android XML layout). The example will showcase the differences in syntax and the approach between the two. The Jetpack Compose version looks like this:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        MyApp()
    }
  }
}
@Composable
fun MyApp() {
  var count by remember { mutableStateOf(0) }
  Column(
    modifier = Modifier.padding(16.dp)
  ) {
      Text(text = "Counter: $count", style = MaterialTheme.typography.bodyLarge)
      Spacer(modifier = Modifier.height(16.dp))
      Button(onClick = { count++ }) {
        Text("Increment")
      }
    }
}

In the preceding example, we have a MyApp composable function that defines the UI for the app. The UI is defined in a declarative manner, by using composables to define the UI and handling state changes using the remember composable. The UI is defined using a functional approach. Also, we can see that the UI is defined in a more concise manner.

With the imperative approach, we must first create the XML UI, as shown in the following code block:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:padding="16dp">
  <TextView
    android:id="@+id/counterTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:text="Counter: 0"
    android:textSize="20sp" />
  <Button
    android:id="@+id/incrementButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@id/counterTextView"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="16dp"
    android:text="Increment" />
</RelativeLayout>

With the layout file created, we can now create the activity class, which will inflate the layout file and handle the button click:

import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
  private var count = 0
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val counterTextView: TextView = findViewById(R.id.counterTextView)
    val incrementButton: Button = findViewById(R.id.incrementButton)
    incrementButton.setOnClickListener {
      count++
      counterTextView.text = "Counter: $count"
    }
  }
}

In this example, the XML layout is inflated in the onCreate method of the MainActivity class, and UI elements are accessed and manipulated programmatically.

In the preceding examples, the Jetpack Compose code is written in Kotlin and provides a more declarative approach, defining the UI in a functional manner. The XML layout, on the other hand, is written imperatively in XML, specifying the UI structure and properties in a more step-by-step manner using XML and interacting with them imperatively in Kotlin code. Jetpack Compose allows for a more concise and expressive representation of the UI using a declarative syntax.

Now that we have a clear understanding of the imperative and declarative ways of writing UIs, in the next section, we will be diving deep into the building blocks of Jetpack Compose.

Composable functions

As shown in Figure 3.1, composable functions are the main building blocks of Jetpack Compose:

Figure 3.1 – Compose UI

Figure 3.1 – Compose UI

A composable function describes how to render a UI. This function must be annotated with the @Composable function. When you annotate a function with this annotation, it means that the function describes how to compose a specific part of the UI. Composable functions are meant to be reusable. They can be called multiple times while the UI is active. Whenever the state of the composable changes, it goes through a process of recomposition, which enables the UI to display the latest state.

Composable functions are pure functions, meaning they don’t have any side effects. They produce the same output when called several times with the same input. This ensures the functions are predictable and efficient in dispatching updates to the UI. However, there are exceptions, for example, launching a coroutine within a composable of calling external methods that do have side-effects, which should be avoided or handled carefully.

Smaller composable functions can be combined to build complex UIs. You can reuse and nest composables inside other composables.

Let’s look at an example of a composable function:

@Composable
fun PacktPublishing(bookName: String) {
    Text(text = "Title of the book is: $bookName")
}

In the preceding code snippet, the PacktPublishing function is annotated with the @Composable annotation. The function takes a parameter, bookName, which is a String. Inside the function, we have another composable from the Material Design library. The composable renders some text to our UI.

When designing our UIs, we usually want to see how the UIs look without running our app. Luckily, we have previews, which visualize our composable functions. We will be learning about them in the next section.

Previews

In Jetpack Compose, we have the @Preview annotation, which generates a preview of our composable function or a group of Compose components inside Android Studio. It has an interactive mode to allow us to interact with our Compose functions. This gives us a way to quickly visualize our designs and easily make changes when needed.

This is how our PacktPublishing composable function would look like with a preview:

@Preview(showBackground = true)
@Composable
fun PacktPublishingPreview() {
    PacktPublishing("Android Development with Kotlin")
}

We have used the @Preview annotation to indicate that we want to build a preview for this function. Additionally, we have set the showBackground parameter to true, which adds a white background to our preview. We have named the function with the Preview suffix. The preview is also a composable function.

To be able to see the preview, you need to be in the split or design mode in your editor. These options are normally at the top right of Android Studio. We also need to do a build for Android Studio to generate a preview, which will look as follows:

Figure 3.2 – Text preview

Figure 3.2 – Text preview

As seen in Figure 3.2, we have a text that displays the string that we passed to the function. The preview also has a white background and its name at the top left.

We can show previews for both dark and light color schemes. We can also configure properties such as the devices and preview windows to be applied.

Previews are great for quick iterations while designing UIs. However, they are not a replacement for actual device/emulator testing, particularly for things such as animations, interactions, or dynamic data.

With an understanding of what previews are and how to create them, let us look into one more Compose feature, modifiers, in the next section.

Modifiers

Modifiers allow us to decorate our composable functions by enabling the following:

  • Change composables’ size, behavior, and appearance
  • Add more information
  • Process user input
  • Add interactions such as clicks and ripple effects

With modifiers, we can change various aspects of our composable, such as size, padding, color, and shape. Most Jetpack Compose components from the library allow us to provide a modifier as a parameter. For example, if we need to provide padding to our preview text, we will have the following:

Text(
    modifier = Modifier.padding(16.dp),
    text = "Title of the book is: $bookName"
)

We have added the padding modifier to the Text composable. This will add 16.dp padding to the Text composable. 16.dp is a density-independent pixel unit in Jetpack Compose. This means it will remain consistent and adjust properly to different screen densities.

We can chain the different modifier functions in one composable. When chaining modifiers, the order of application is crucial. If we don’t achieve the desired result, we need to double-check the order. Let’s observe this concept in practice:

Text(
    modifier = Modifier
        .fillMaxWidth()
        .padding(16.dp)
        .background(Color.Green),
    text = "Title of the book is: $bookName"
)

We have added two more modifiers. The first is the fillMaxWidth modifier, which is added to the text composable. This will make the text composable take the full width of the parent. The other one is the background modifier to the Text composable. This will add a background color to the text composable. The preview for our text will look as follows:

Figure 3.3 – Text modifier preview

Figure 3.3 – Text modifier preview

As seen in the preceding screenshot, the text now occupies the whole width of the device and has a green background. It also has a padding of 16dp all around.

Modifiers do not modify the original composable. They return a new, modified instance. This ensures our composable remains unchanged and immutable. Immutability, a fundamental principle in functional programming, ensures that the state remains unchanged, simplifying state management and reducing side effects. This approach enhances predictability and readability by adhering to the principles of referential transparency. The ability to compose functions, exemplified by chaining modifier functions, facilitates a concise and readable expression of complex UI behavior without altering the original composable. In addition to using the existing modifiers, we can also create our own modifiers when needed.

Now that you have an understanding of what modifiers are, we are going to build on that knowledge by learning about Jetpack Compose layouts in the next section.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image