David's Blog

Android Architecture Components A Comprehensive Overview

By David Li on 2023-08-17T05:09:31.000Z

Android Architecture Components: A Comprehensive Overview

Android Architecture Components (AAC) are a collection of libraries designed to help developers build robust, maintainable, and testable applications. Developed by Google, these components provide a solid foundation for creating applications that adhere to best practices and architectural patterns. In this article, we will delve into the various components of AAC, explaining their usage and benefits to Android developers.

Table of Contents

  1. Introduction
  2. LiveData
  3. ViewModel
  4. Room Persistence Library
  5. DataBinding
  6. WorkManager
  7. Navigation Component
  8. Conclusion

Introduction

In the past, Android developers faced several challenges while creating applications, such as handling lifecycle events, managing UI components, or persisting data. Android Architecture Components provide a set of tools and guidelines to address these issues, making it easier for developers to craft scalable and maintainable apps.

Here is a list of key AAC components that we will discuss in this article:

  • LiveData
  • ViewModel
  • Room Persistence Library
  • DataBinding
  • WorkManager
  • Navigation Component

LiveData

LiveData is an observable data holder class designed to handle lifecycle-aware data. It respects the lifecycle of other app components, such as Activities, Fragments, or Services, and automatically manages subscription and unsubscription operations. LiveData ensures that UI components receive updates only when they are in an active state, avoiding common issues like memory leaks or crashes due to stopped activities.

Advantages

  • Ensures UI components are only updated when they are active.
  • Reduces boilerplate code for handling lifecycle events.
  • Provides a consistent way to observe data changes across the application.

Usage

To use LiveData, wrap your data source with LiveData and expose it through a method. You can then observe the LiveData instance in your UI components, such as Activities or Fragments, providing a LifecycleOwner and an Observer for handling data updates.

class UserViewModel : ViewModel() {
    private val _userLiveData = MutableLiveData<User>()
    val userLiveData: LiveData<User> get() = _userLiveData

    fun loadUser(userId: String) {
        // Load user data from a repository and update _userLiveData
    }
}

class UserActivity : AppCompatActivity() {
    private lateinit var userViewModel: UserViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user)

        userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)

        userViewModel.userLiveData.observe(this, Observer { user ->
            // Update UI with the new user data
        })

        // Load user data        val userId = "123"
        userViewModel.loadUser(userId)
    }
}

ViewModel

ViewModel is a class designed to store and manage UI-related data in a lifecycle-conscious way. Its main purpose is to separate data handling from UI components, such as Activities or Fragments, and to preserve data during configuration changes, such as screen rotations. ViewModel instances are automatically retained during configuration changes and are not destroyed until their associated UI component is permanently removed.

Advantages

  • Preserves data during configuration changes.
  • Separates data handling from UI components, promoting a clean architecture.
  • Simplifies testing by allowing developers to test ViewModel classes independently from UI components.

Usage

To create a ViewModel, extend the ViewModel class and expose your data through properties or methods. You can then obtain an instance of your ViewModel in your UI component using the ViewModelProvider class.

class CounterViewModel : ViewModel() {
    private var _count = MutableLiveData<Int>(0)
    val count: LiveData<Int> get() = _count

    fun increment() {
        _count.value = _count.value?.plus(1)
    }
}

class CounterActivity : AppCompatActivity() {
    private lateinit var counterViewModel: CounterViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_counter)

        counterViewModel = ViewModelProvider(this).get(CounterViewModel::class.java)

        counterViewModel.count.observe(this, Observer { count ->
            // Update UI with the new count
        })

        // Increment the count on button click
        findViewById<Button>(R.id.increment_button).setOnClickListener {
            counterViewModel.increment()
        }
    }
}

Room Persistence Library

Room is a persistence library that provides an abstraction layer over SQLite, helping developers create robust and maintainable database applications. It offers compile-time checks for SQL queries, reducing the risk of runtime errors, and simplifies database access by using annotations and Kotlin/Java objects.

Advantages

  • Provides type-safe and compile-time checked SQL queries.
  • Reduces boilerplate code for database operations.
  • Easily integrates with other Architecture Components, such as LiveData and ViewModel.

Usage

To use Room, define your data entities as Kotlin/Java classes and annotate them with @Entity. Create a Data Access Object (DAO) interface for each entity, and annotate the interface with @Dao. Finally, define a Room database by extending the RoomDatabase class and annotating it with @Database. You can then build an instance of your Room database and use it to perform database operations.

// Define the User entity
@Entity
data class User(
    @PrimaryKey val id: String,
    val name: String,
    val age: Int
)

// Create a DAO for the User entity
@Dao
interface UserDao {
    @Query("SELECT * FROM user WHERE id = :userId")
    fun getUser(userId: String): LiveData<User>

    @Insert
    fun insertUser(user: User)
}

// Define the Room database
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

// Build an instance of the Room database
val db = Room.databaseBuilder(
    context,
    AppDatabase::class.java,
    "app_database"
).build()

// Use the database instance to perform operations
val userDao = db.userDao()
val user = User("123", "John Doe", 25)
userDao.insertUser(user)
userDao.getUser("123").observe(this, Observer { user ->
    // Update UI with the new user data
})

DataBinding

DataBinding is a support library that allows developers to bind UI components directly to data sources, reducing the need for boilerplate code to update the UI when data changes. It supports two-way data binding, meaning that changes in the UI can automatically update the data source, and vice versa.

Advantages

  • Reduces boilerplate code for updating the UI.
  • Provides a clear separation of UI and data handling code.
  • Improves performance by minimizing UI updates and avoiding unnecessary operations.

Usage

To enable DataBinding in your project, add the following to your app’s build.gradle file:

android {
    ...
    buildFeatures {
        dataBinding true
    }
}

Next, wrap your layout XML with a <layout> tag and add <data> elements to define variables. In your UI component, use the generated binding class to inflate the layout and bind the data to the UI.

<!-- activity_main.xml -->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.example.User" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:id="@+id/user_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}" />

        <TextView
            android:id="@+id/user_age"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{Integer.toString(user.age)}" />
    </LinearLayout>
</layout>
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Inflate the layout using DataBindingUtil
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        // Bind the data to the UI
        val user = User("123", "John Doe", 25)
        binding.user = user
    }
}

WorkManager

WorkManager is a library for managing deferrable, guaranteed background work in Android applications. It takes care of compatibility issues and provides a clean API for scheduling tasks, even when the app is not running or the device is rebooted. WorkManager chooses the best way to run your tasks, depending on factors such as the device’s API level and the app’s constraints.

Advantages

  • Simplifies background task scheduling and execution.
  • Ensures tasks are executed, even when the app is not running or the device is rebooted.
  • Handles compatibility and API level differences gracefully.

Usage

To use WorkManager, create a class that extends Worker and override the doWork() method. This method contains the code that will be executed when your task is run. You can then create a WorkRequest to schedule your task, and a WorkManager instance to enqueue the request.

// Define a Worker class
class MyWorker(context: Context, workerParams: WorkerParameters) :
    Worker(context, workerParams) {
    override fun doWork(): Result {
        // Perform your background task here
        return Result.success()
    }
}

// Schedule a task using WorkManager
val workRequest
© Copyright 2024 by FriendlyUsers Tech Blog. Built with ♥ by FriendlyUser. Last updated on 2024-04-29.