Andrés Cruz

Master Guide to Modern Android Development: From Kotlin to Jetpack Compose

The evolution of Android application development has been a fascinating journey, marked by constant innovations that seek to simplify and enhance the creation of mobile experiences. In this pillar article, we will explore two of the fundamental pillars of Android modernization: Jetpack Compose for building declarative user interfaces and Kotlin as the preferred programming language. Both technologies have radically transformed the way developers approach their projects, offering greater efficiency, conciseness, and robustness. This comprehensive content is designed to be the definitive guide, covering everything from basic concepts to advanced implementations, and is based on a selection of key publications from the DesarrolloLibre blog.

From managing coroutines and data persistence with Kotlin, to implementing complex animations and adaptable layouts with Compose, this guide breaks down the key concepts and best practices for building cutting-edge Android applications. Get ready to master native application development with the most relevant technologies of today.

Part 1: Kotlin Fundamentals, Architecture, and Tools

Before diving into the user interface, it is crucial to master the language and tools that make modern Android development possible. Kotlin is not only a concise language, but it also offers powerful mechanisms to handle asynchronous operations, data management, and application architecture.

Kotlin in Android application development

The origins of application development in Android were in Java, which is clearly no longer the case. Since we started creating applications with Eclipse with the ADT plugin, Android development has changed a lot. It all began when, due to licensing issues with Java now being owned by Oracle, it became an old language with quite convoluted or complex syntax, among other considerations or negative points that ultimately hinder Android application development. As a result, Google has decided to gradually replace Java with another programming language that at least tries to be at the same level as Java as the programming language par excellence for Android application development with Android Studio.

What is Kotlin? - Kotlin as a possible candidate to replace Java

Kotlin is a programming language that is much simpler, more pragmatic, and more modern than Java 7 (which is what Android internally uses), being able to compile and run on the Java Virtual Machine (JVM) without major problems, allowing it to work with libraries or other files, classes, among others; that are written in Java without any problem; this makes it a powerful candidate when developing applications in Android.

Kotlin is the programming language of the moment, fashionable, whose development began in 2010 with the company JetBrains and has gained prominence thanks to Google until it decided to adopt it to develop applications in Android through Android Studio.

Starting from Android Studio 3.0 (remember that Android Studio is the official Software Development Environment for the development of applications for Android; you can see the installation steps as well as general information about it in the following link: Getting started with Android Studio) Google has incorporated important changes that it has made over the years, the main one being that we now use Kotlin instead of Java for the development of applications in Android.

Kotlin's interoperability with Java to create cross-developments

One of the strong points that interested Google in opting for Kotlin as a programming language for Android, and adding support in the star or excellent IDE for developing Android applications, which is Android Studio, is 100% interoperability with Java; this does not mean limiting the project to coding it entirely in Kotlin (which is a completely valid option) but we can also exchange or interoperate the code with Java when we deem it necessary.

We will talk about variables and data types in Kotlin, also, we will give a "Hello World" in this programming language; as we already know, Kotlin is a programming language that serves us much more than just for Android since its purpose is to be general-purpose, statically typed, object-oriented but also allows functional programming and runs on the Java Virtual Machine.

The first thing we will see here, apart from the previous video tutorial, is how to create a project in IntelliJ IDEA, which is the official IDE of JetBrains, who are the creators of this programming language. It is free (it has a Community version) and you can download it from their official website.

Variables in Kotlin var and val

In Kotlin, we have two ways to declare variables, using the reserved keywords var and val:

  • var: We use this reserved keyword to define mutable variables, that is, variables that change over time.
  • val: We use this reserved keyword to define immutable variables (constants), that is, variables that do not change over time.
var edad = 28
val nombre = "Andres" 

Immutable or read-only variables, as their name indicates, cannot be altered:

val language = "English"
language = "French" // Error

For mutable variables, we can alter them:

var score = 10
score = 20 // OK

As a recommendation, whenever possible, we should use immutable variables. With this, we gain points in security and avoid headaches by not allowing us to "unintentionally" or by layer 8 error alter the value of a variable that should not vary; in addition, the IDE helps us by marking immutable variables in a different color from mutable ones, and also if we declare a variable as mutable and it notes that it does not vary during the application's lifetime, it recommends changing it to immutable or val.

Original source: Variables and data types in Kotlin

 

If - Else Conditional

The if conditional works similarly to how we do it in Java, although with some extra features that enhance it and allow us to write cleaner and more readable code (and with fewer lines).

Its traditional structure:

var edad = 10
if (edad > 18) {
    println("Eres mayor de edad")
} else {
    println("Eres menor de edad")
}

If the code block inside the if or else is a single line, we can omit the braces:

if (edad > 18) println("Eres mayor de edad")
else println("Eres menor de edad")

Nothing new here, but in Kotlin, if can be used as an expression, which means it returns a value; if we have the following example:

var edad = 18
var esMayor = false
if (edad >= 18) {
   esMayor = true
} else {
   esMayor = false
}
print(esMayor)

We can simplify it by using if as an expression:

val edad = 18
val esMayor = if (edad >= 18) true else false
print(esMayor)

Or we can even place code blocks:

val edad = 18
val esMayor = if (edad >= 18) {
    println("Es mayor de edad")
    true
} else {
    println("Es menor de edad")
    false
}
print(esMayor)

In this case, the last line of the code block is the one that is returned and assigned to the variable.

Parameters and Named Arguments in functions

Parameters are separated by commas, as in 99% of languages. A very useful feature is the use of named arguments.

This allows invoking a function by specifying the parameter name. It is extremely useful when functions have many arguments or when the order of the arguments is not obvious. Furthermore, by using names, you can reverse the order of the parameters when calling it without affecting the result, as the compiler knows exactly which variable you are referring to.

fun sum(x: Int, y: Int): Int {
    return x + y
}
fun main() {
    println(sum(1, 2))
    // 3
}

Original source: Functions in Kotlin

Classes in Kotlin

Classes in Kotlin as we will see are simplified to write the most functionality with the least amount of code. We have already seen the use of functions in Kotlin, as a key piece for code reuse, let's go to the next block, classes.

We also have special classes to declare data and the use of Companion Objects.

Part 2. Android Studio and the Kotlin Ecosystem

Kotlin, the official programming language of Android Studio, has replaced Java thanks to its modern syntax and type safety. When starting a project in Android Studio, we find a structure that favors the separation of concerns and modularity.

Project Setup and Structure 

When creating a new project ("Getting started with Kotlin"), Android Studio generates a key structure:

  • app/src/main/java (or kotlin): This is where the source code resides. MainActivity.kt is the entry point, inheriting from ComponentActivity to integrate with the Android lifecycle.
  • res (Resources): Contains layouts (legacy XML), strings, themes, and drawables. Although Compose reduces the dependence on XML for layouts, this directory remains vital for static resources.
  • Gradle: The build.gradle.kts files manage dependencies and SDK versions. It is here where we define the minSdk and targetSdk, ensuring the compatibility of our app.

Shortcuts and Productivity

To develop like an expert, mastering keyboard shortcuts is essential:

  • Ctrl + E: Recent files. Allows quick navigation between active files.
  • Alt + Enter: Intention actions. Corrects errors, imports libraries, and optimizes code automatically.
  • Ctrl + Alt + L: Reformat code. Maintains consistent style and indentation.
  • Ctrl + Alt + M: Extract method. Refactors code blocks into reusable functions.

2. Debugging and Logs with Logcat

Debugging is a fundamental skill. While println() can work in basic tests, Android provides the Log class for structured and filterable logging through Logcat.

Log Structure

We use a structure of Tag (Key) and Message (Value). The Tag allows us to filter messages in the Logcat console (for example, using the class name "MainActivity").

Priority Levels

It is vital to use the appropriate level for each message:

  • Log.e (Error): Critical failures that break functionality. Displayed in red.
  • Log.w (Warning): Warnings about unexpected but recoverable behaviors.
  • Log.i (Info): General information about the application flow.
  • Log.d (Debug): Useful data during development (variables, states).
  • Log.v (Verbose): Very detailed information, useful only in deep debugging.

In Kotlin, it is good practice to define the TAG as a constant: private const val TAG = "MiApp".

3. Asynchronous Programming: From AsyncTask to Coroutines

Handling background operations is one of the most drastic changes in modern development. The "Legacy" approach with AsyncTask or manual threads suffered serious problems with the lifecycle (memory leaks when rotating the screen).

Kotlin Coroutines: The Modern Approach

Coroutines are lightweight threads that are lifecycle-aware. They allow writing asynchronous code sequentially. Key features:

  • suspend fun: Marks a function that can pause its execution without blocking the main thread.
  • viewModelScope: A coroutine scope tied to the ViewModel's lifecycle. Any coroutine launched here is automatically canceled if the ViewModel is destroyed.
  • lifecycleScope: Similar, but tied to an Activity or Fragment.

Usage Example

To simulate a network operation or a database without blocking the UI:


viewModelScope.launch {
    delay(2000) // Suspends without blocking
    Log.d(TAG, "Operation completed")
}

4. Data Persistence and State Management

Persisting data means that information survives app closure or device reboot. Modern Android offers a hierarchical architecture for this.

DataStore vs SharedPreferences

SharedPreferences (Legacy) allowed saving simple key-value pairs but had performance problems (synchronous blocking). DataStore is Jetpack's modern replacement:

  • Uses Coroutines and Flow for asynchronous operations.
  • Is safe against runtime exceptions.
  • Ideal for user preferences (dark mode, flags).

Room Database (Modern SQLite)

For structured data and relationships, Room is the official abstraction layer over SQLite. It consists of three elements:

  • @Entity: Defines the table structure as a Kotlin data class.
  • @Dao (Data Access Object): Interface that defines read/write methods (Insert, Query) using suspended functions.
  • @Database: Abstract class that connects entities with DAOs.

UI State Management (ViewModel)

The ViewModel is designed to store and manage UI-related data in a lifecycle-aware manner. It survives configuration changes such as screen rotation. In combination with StateFlow or LiveData, it allows the interface to react automatically to data changes.

Part 2: Building Modern Interfaces with Jetpack Compose

Jetpack Compose has revolutionized UI creation in Android. Abandoning the XML View system, Compose uses a declarative approach where the interface is a function of the state. "Recomposing" is the process of updating the UI when the state changes.

1. Fundamental Action Components: Buttons

Buttons are the basic unit of interaction. In Compose, all share the premise of receiving an onClick and content (usually text or icon), but vary in visual style.

  • Button (Filled): Elevated and with solid fill color. For primary actions.
  • OutlinedButton: With border and transparent background. For secondary or less emphasized actions.
  • TextButton: No borders or background, only text. Ideal for dialogues or cards.
  • FloatingActionButton (FAB): Circular floating button for the main action on the screen. It is commonly integrated into the Scaffold.

To organize buttons, we use Column (vertical) or Row (horizontal), applying modifiers such as padding for spacing.

Or organize our content with Tabs with HorizontalPager.

2. Navigation and Structure: Menus and Drawers

Navigation is key to the user experience. Compose offers flexible components for this.

Dropdown Menus

Dropdown menus or "options menu" are essential. They are built with DropdownMenu and DropdownMenuItem. Their visibility is controlled by a boolean state (expanded) that is toggled on click.

Navigation Drawer (Side Menu)

The ModalNavigationDrawer is the modern container for side menus.

  • Requires a DrawerState to programmatically control its opening/closing (using coroutines for animation).
  • Content is defined in ModalDrawerSheet, where we can list navigation options.
  • It wraps the main content (usually a Scaffold), allowing the menu to overlap smoothly.

Bottom Sheets

Bottom Sheets are panels that usually appear from the bottom and are ideal for storing secondary information and not overloading the main interface.

3. Efficient Lists: LazyColumn and LazyRow

Unlike Column or Row, which render all their children simultaneously (inefficient for large lists), "Lazy" components only render visible elements on screen, recycling views when scrolling.

  • LazyColumn: Vertical equivalent of the old RecyclerView.
  • LazyRow: For horizontal lists (carousels).
  • LazyVerticalGrid: For adaptive grids. GridCells.Adaptive is very powerful for automatically adjusting columns according to screen width.

These components are fundamental for performance when handling dynamic or extensive data collections.

4. Feedback and Dialogues: Snackbar, Notifications and AlertDialog

Communicating the outcome of actions is vital.

Dialogs (AlertDialog)

Modal windows that require user attention. They are controlled by a simple boolean state. Compose offers pre-styled AlertDialog (title, text, confirm/cancel buttons) or Dialog for 100% customized content.

Snackbar

Brief messages at the bottom of the screen. Its implementation requires:

  • A SnackbarHostState defined within a remember scope.
  • A SnackbarHost placed inside the Scaffold.
  • An asynchronous call (LaunchedEffect or coroutine) to showSnackbar.

Notifications

Messages like push notifications that we can send and configure in a very personal way.

5. Graphics and Visualization: Canvas

When standard components are not enough, Canvas gives us a blank canvas.

  • Allows drawing primitives: circles, rectangles, lines, arcs.
  • It is ideal for custom graphics, data visualizations, or artistic designs.
  • Coordinates are based on X and Y, and we can apply transformations such as rotation or translation.

Unlike the Legacy system (which required inheriting from View and overriding onDraw), in Compose a Canvas is simply another Composable within your UI.

6. Advanced Functionalities and Integrations

QR Code Generation

By integrating libraries like ZXing, we can dynamically generate QR codes. The logic involves creating a bit matrix (BitMatrix) from text and then converting it into an Android Bitmap that Compose can render as an image (ImageBitmap).

Google Maps

The integration of maps has been greatly simplified. Using the Google Maps library for Compose:

  • The API Key is managed in the Manifest.
  • The GoogleMap composable handles the lifecycle internally.
  • We can add Markers and control the camera reactively by passing position states (Lat/Lng).

WebView

To display web content, we use AndroidView to embed the classic WebView component within Compose. It is crucial to configure the WebViewClient to control navigation and prevent the external browser from opening, keeping the user in the app.

We can also use specific functions like embedding YouTube videos.

Also, make HTTP requests to consume Rest APIs via Retrofit.

MediaPlayer 

Playing audio on the internet or locally is very easy with the MediaPlayer API and pausing or playing audio easily.

Sensors: Accelerometer

Access to hardware like the accelerometer still depends on SensorManager. In Compose, we use DisposableEffect to register and unregister the sensor listener, ensuring no memory leaks when the composable leaves the screen. Sensor values (X, Y, Z) are stored in mutable states so the UI updates in real time (e.g., moving a ball across the screen).


This guide covers the foundations of current Android development. The combination of Kotlin's robustness (coroutines, type-safe) with Jetpack Compose's declarative flexibility (Lazy lists, states, reactive UI) allows building maintainable, scalable, and visually impactful applications. The path of an Android developer is one of continuous learning, but these pillars will give you a solid base to face any challenge.

Key Concepts

What is an apk?

A file with the .apk extension (Application Package File) is nothing more than a packaged file of an application for the Android Mobile Operating System; which is compiled and packaged into a single file, everything that is included in an Android application:

  • AndroidManifest.xml file.
  • .dex files (Dalvik Executable; files that run on the Dalvik virtual machine).
  • resources folder.
  • assets folder.

Understand "Android application" as: games, players, readers, or any program developed for this platform; in general, an .apk file can have any name but must have the .apk extension.

This format is used to distribute and install applications for the Android platform.

An APK is the installer on Android devices, used to distribute and install Android applications; that is, it is the equivalent of Windows .exe files but for Android.  

These executables contain the compiled code provided by a solution with Android Studio; this compressed file includes the application code, and other associated resources such as images and in general any other information necessary for the application to work. APK files can be downloaded from Google Play, which is the premier application store on Google's Android, but they can be distributed in other stores or from websites; in any case, it is important to be careful when downloading APK files as they may contain malware or viruses, especially from third-party sites.