This week marks the first beta release of Jetpack Compose. Seeing those platform changes happening, I curated recommended technologies, libraries, and frameworks to be used in new projects, based on my previous experience, gut feelings, and everything that is being released right now. For now, I'm citing the topics and later each one will become a thorough and independent article in this blog.
Kotlin
Kotlin is an exciting programming language designed to be concise and expressive. It's Google's recommended programming language for Android since 2019 and has great tooling support on Android Studio. Kotlin allows the programmer to write safer code and android apps written in Kotlin are 20% less likely to crash (compared to old Java versions), thanks to features like type and null safety. It combines aspects of object-oriented and functional programming paradigms to allow the programmer to write better code.
Access https://developer.android.com/kotlin/first to start learning Kotlin-first Android development.
Android Jetpack
Historically, Android wasn't an opinionated platform, regarding app architecture and modularization. Things like backward compatibility, navigation, state management, and app lifecycle were error-prone and slowed app development. Hence, the Android team decided to create a suite of libraries to help developers write code that follows best practices, with less boilerplate, and is consistent across Android versions and devices.
Some of the libraries, like Jetpack's LiveData and Databinding were a first step towards declarative UI. They're now being replaced with better alternatives like Compose UI and Flow, and albeit they're practically alpha, it's not recommended anymore to be used in new projects.
Beware: You're about to meet some Android Studio bugs and Compose UI tooling issues while using Android Studio Canary. But that's ok, they're expected to be polished throughout the year and aren't blocking the work.
These are the up to date, recommended Jetpack libraries to be used in new projects:
1. Jetpack Compose
Jetpack Compose is a declarative, modern approach for building user interfaces in Android and is intended to replace the old way of doing Android UI. It allows you to build UI without the need to mutate the views whenever data changes.
"In Compose's declarative approach, widgets are relatively stateless and do not expose setter or getter functions."
This means that the UI observes a state and all the screen is regenerated based on any changes on this state. The smartness here is that Compose knows which view must be updated, and only redraws the necessary portion of the screen, avoiding excessive calculations.
It has special tooling available in Android Studio preview, like smart editor features and Compose UI preview.
Get started on https://developer.android.com/jetpack/compose
2. ViewModel
One of the most important concepts needed to develop an Android App is the concept of the lifecycle. In the platform, every main building block has a lifecycle that is controlled mainly by the operating system. So, your app isn't really creating an Activity or a Fragment by itself, it is asking the OS to provide an instance of one or another. When the system has a memory pressure, it starts releasing resources and a part (or the whole) of your app may be destroyed.
"Doing the right work at the right time and handling transitions properly make your app more robust and performant. For example, good implementation of the lifecycle callbacks can help ensure that your app avoids:
- Crashing if the user receives a phone call or switches to another app while using your app.
- Consuming valuable system resources when the user is not actively using it.
- Losing the user's progress if they leave your app and return to it at a later time.
- Crashing or losing the user's progress when the screen rotates between landscape and portrait orientation."
To understand why ViewModel was proposed and how it works, just look at the lifecycle of a ViewModel side by side with the Activity lifecycle.
ViewModel survives configuration changes and is only released when the Activity is finished. Another important usage of a ViewModel, is when you need to share state between Fragments. A ViewModel can outlive each of the Fragments, with its scope bound to the lifecycle of the enclosing Activity.
The ViewModel name comes from an architectural pattern called MVVM (Model-View-ViewModel). Architectural patterns are ways of organizing your code to achieve higher maintainability of your software in the long run. Different patterns arise accordingly to the task being executed, platform, team's size, and other characteristics of the project. I intend to discuss these architectural topics on this blog in the future.
Learn how to create and use a ViewModel in https://developer.android.com/topic/libraries/architecture/viewmodel
3. Room
Using relational databases is an important part of developing applications. Android traditionally has good support for SQLite, a lightweight database manager. SQlite is very useful for persisting and organizing non-volatile data, but to use it you have to face some challenges. It's low-level APIs demands a lot of the developer's time to be used and maintained:
- "There is no compile-time verification of raw SQL queries. As your data graph changes, you need to update the affected SQL queries manually. This process can be time-consuming and error-prone."
- "You need to use lots of boilerplate code to convert between SQL queries and data objects."
The Room persistence library was created as an abstraction layer that allows developers to have compile-time verification of SQL queries, annotations to reduce boilerplate code, and tools to ease database migrations.
Example of a User Data Access Object with its annotations. Note that insert and delete annotations didn't require writing SQL queries by hand.
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll(): List<User>
@Query("SELECT * FROM user WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List<User>
@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
"last_name LIKE :last LIMIT 1")
fun findByName(first: String, last: String): User
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
Room's training on developer.android.com: https://developer.android.com/training/data-storage/room
4. Paging v3
Whenever you have lots of data coming from a data source, be it an API or a database, usually some kind of pagination is needed to meet speed and bandwidth requirements, for instance. With pagination, only a subset of the data is served (and rendered), and the user can ask for more data later if needed.
"The components of the Paging library are designed to fit into the recommended Android app architecture, integrate cleanly with other Jetpack components, and provide first-class Kotlin support. The Paging library includes the following features:
- In-memory caching for your paged data. This ensures that your app uses system resources efficiently while working with paged data.
- Built-in request deduplication, ensuring that your app uses network bandwidth and system resources efficiently.
Configurable RecyclerView adapters that automatically request data as the user scrolls toward the end of the loaded data.(Recycler view isn't needed anymore on Jetpack Compose 🥳)- First-class support for Kotlin coroutines and Flow, as well as LiveData and RxJava.
- Built-in support for error handling, including refresh and retry capabilities."
The following diagram illustrates how Paging fits into the typical architecture of an Android application. Now that we have Jetpack Compose, the PagingDataAdapter is not needed anymore, a composable function watches PagingData state on ViewModel.
Paging v3 library documentation: https://developer.android.com/topic/libraries/architecture/paging/v3-overview
5. Navigation
In the beginning, the Android team created the Activity.
Before Android 3.0 (Honeycomb), the typical android app worked swapping activities and asking for third-party activities to be displayed by the operating system. With the arrival of Android tablets, this abstraction wasn't sufficient anymore. Usually, a tablet can show master and detail views simultaneously, and rewriting the whole app for tablets wasn't an option. Hence, Fragments were created with the mission to be autonomous and combined within Activities, which acted as a wrapper for one or more fragments. The navigation now was mainly changing between app fragments. Any app that has more than one screen needs some type of navigation system, even if done manually without any tooling help.
Navigation on Android isn't a simple task. Managing aspects like the up button™ and back button™, deep linking, fragment transactions and animated transitions, fragment arguments, and a plethora of specificities can be hard. Jetpack Navigation helps to implement navigation in a correct and principled way.
New: On Jetpack Compose, you can project your UI without using Fragments, View, and XML files. Everything is done on Kotlin, including navigation with NavHost and NavController.
Learn to use Navigation with Compose in https://developer.android.com/jetpack/compose/navigation
Hint: The documentation doesn't include the imports needed to complete the training. As navigate(route:String) is an extension function (not included by default in NavController), you may need to add the following import:
import androidx.navigation.compose.navigate
When using popUpTo(route:String)
, you need to add the second argument inclusive: Boolean
that isn't cited in the official documentation. So your code will look like this:
// Pop everything up to the "home" destination off the back stack before
// navigating to the "friends" destination
navController.navigate(“friends”) {
popUpTo("home") { inclusive = false }
}
// Pop everything up to and including the "home" destination off
// the back stack before navigating to the "friends" destination
navController.navigate("friends") {
popUpTo("home") { inclusive = true }
}
6. Hilt (Jetpack)
Hilt is a dependency injection library for Android that reduces the boilerplate of doing manual dependency injection in your project. Doing manual dependency injection requires you to construct every class and its dependencies by hand, and to use containers to reuse and manage dependencies.
1.Why dependency injection (DI)?
R. DI makes unit testing easier, as it allows the injection of test doubles instead of implementations. Also, it helps reduce coupling, because it forces the developer to program for an interface, instead of an implementation.
2.Why coupling is bad?
R. Because the code you make today will have to be changed, adapted, maintained by someone (or yourself). When your code is tightly coupled, refactors are hard and any small change may break your system.
3. Does DI cause any adverse effects?
R. Debugging can be harder, because of all the indirections caused by the presence of a dependency injection system. Many DI frameworks have a steep learning curve, a considerable barrier to beginners.
Hilt provides a standard way to use DI in your application by providing containers for every Android class in your project and managing their lifecycles automatically. Hilt is built on top of the popular DI library Dagger to benefit from the compile-time correctness, runtime performance, scalability, and Android Studio support that Dagger provides.
Hilt documentation: https://developer.android.com/training/dependency-injection/hilt-android
Top comments (0)