Dagger 2: Tìm hiểu Dependency Injection cho Android

Dependency Injection (DI) là một kỹ thuật quan trọng giúp xây dựng ứng dụng Android dễ bảo trì, kiểm thử và mở rộng. Bài viết này giới thiệu Dagger 2, một framework DI mạnh mẽ và hiệu quả, đồng thời làm rõ các khái niệm liên quan đến DI.

Ảnh minh họa Dagger 2Ảnh minh họa Dagger 2

Chuẩn bị: Nguyên tắc SOLID và Dependency Inversion

Trước khi đi sâu vào Dagger 2, bạn nên nắm vững các nguyên tắc SOLID, đặc biệt là nguyên tắc Dependency Inversion (DIP):

Nguyên tắc Dependency Inversion:

  1. Các module cấp cao không nên phụ thuộc vào các module cấp thấp. Cả hai nên phụ thuộc vào abstraction.
  2. Abstraction không nên phụ thuộc vào chi tiết, mà ngược lại. (Các class giao tiếp với nhau thông qua interface, không phải thông qua implementation.)

Trong lập trình truyền thống, module cấp cao thường gọi trực tiếp module cấp thấp, tạo ra sự phụ thuộc. Khi module cấp thấp thay đổi, module cấp cao cũng phải thay đổi theo, gây khó khăn cho việc bảo trì và mở rộng. DIP giải quyết vấn đề này bằng cách yêu cầu cả hai module phụ thuộc vào một abstraction (interface). Nhờ đó, module cấp thấp có thể thay đổi mà không ảnh hưởng đến module cấp cao.

Định nghĩa và khái niệm DI:

Nhiều người nhầm lẫn giữa Dependency Inversion, Inversion of Control (IoC) và Dependency Injection (DI). Chúng có liên quan nhưng không hoàn toàn giống nhau:

  • Dependency Inversion: Là một nguyên tắc thiết kế code.
  • Inversion of Control: Là một design pattern để tuân thủ DIP. DI là một trong những cách hiện thực IoC.
  • Dependency Injection: Là một kỹ thuật hiện thực IoC, trong đó các dependency được “inject” vào module cấp cao.

Sự khác biệt giữa Dependency Inversion, Inversion of Control và Dependency InjectionSự khác biệt giữa Dependency Inversion, Inversion of Control và Dependency Injection

Hiểu một cách đơn giản, Dependency Injection hoạt động như sau:

  • Các module giao tiếp thông qua interface. Module cấp thấp implement interface, module cấp cao gọi module cấp thấp thông qua interface. Ví dụ: Để giao tiếp với database, ta có interface IDatabase. Các module như XMLDatabase, SQLDatabase implement interface này. Module CustomerBusiness chỉ sử dụng IDatabase.
  • Việc khởi tạo các module cấp thấp do DI Container thực hiện. Ví dụ: Trong CustomerBusiness, ta không khởi tạo IDatabase db = new XMLDatabase(). DI Container sẽ thực hiện việc này. CustomerBusiness không cần biết về XMLDatabase hay SQLDatabase.
  • Việc module nào gắn với interface nào được cấu hình trong code hoặc file XML.
  • DI giúp giảm sự phụ thuộc, dễ dàng thay đổi module, bảo trì code và testing.

Dependency Injection với Dagger 2

Dagger 2 là gì?

Dagger 2 là một dependency injector được thiết kế cho các thiết bị Android. Khác với các DI framework khác sử dụng reflection (ví dụ như Spring IoC), Dagger 2 sử dụng annotation processor để tạo code trong quá trình biên dịch. Điều này giúp:

  • Tăng hiệu năng: Tránh overhead của reflection.
  • Dễ debug: Code được tạo ra dễ đọc và debug hơn.
  • Giảm kích thước ứng dụng: Loại bỏ thư viện reflection.

Dagger 2 không mạnh mẽ bằng các DI framework enterprise, nhưng nó nhẹ nhàng, dễ sử dụng và dễ debug hơn, phù hợp cho ứng dụng Android.

Dagger 2 sử dụng các annotation chính sau:

  • @Module & @Provides: Định nghĩa class và method cung cấp dependency.
  • @Inject: Yêu cầu một dependency. Có thể dùng với constructor, field hoặc method.
  • @Component: Interface kết nối giữa module và injection.

Định nghĩa Dependency Providers (Object Providers)

Trong Dagger 2, class đánh dấu @Module có trách nhiệm cung cấp các object có thể được inject. Các class này định nghĩa các phương thức được đánh dấu @Provides. Các object trả về từ các phương thức này sẽ có sẵn để inject.

Ví dụ:

@Module
public class AppModule {
    @Provides
    public SharedPreferences provideSharedPreferences(Application application) {
        return application.getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
    }
}

Trong ví dụ trên, AppModule cung cấp một instance của SharedPreferences.

Định nghĩa Dependency (Object Consumers)

Bạn sử dụng @Inject để định nghĩa dependency. Nếu bạn đặt @Inject với constructor, Dagger 2 sẽ sử dụng instance này để hoàn thành dependency. Điều này giúp tránh việc định nghĩa nhiều phương thức @Provider cho các đối tượng này.

Ví dụ:

public class MyViewModel {
    private final SharedPreferences sharedPreferences;

    @Inject
    public MyViewModel(SharedPreferences sharedPreferences) {
        this.sharedPreferences = sharedPreferences;
    }
}

MyViewModel phụ thuộc vào SharedPreferences. Dagger 2 sẽ inject SharedPreferences vào constructor của MyViewModel.

Kết nối Consumers và Providers

@Component được sử dụng trên một interface. Interface này được Dagger 2 sử dụng để tạo code, kết nối giữa module và đối tượng thể hiện sự phụ thuộc.

Bảng tổng quan cách sử dụng annotation trong Dagger 2:

Annotation Usage
@Module Dùng cho các class chứa methods được đánh dấu @Provides.
@Provides Dùng cho các methods trong class @Module để cung cấp objects cho dependency injection.
@Singleton Tạo và chia sẻ một instance duy nhất của object được cung cấp.
@Component Dùng cho một interface. Dagger 2 sử dụng interface này để tạo code sử dụng modules để đáp ứng dependency.

Lưu ý: Dagger không tự động inject các fields private. Nếu muốn inject field, bạn cần define một method trong @Component interface, trong đó lấy instance muốn inject làm tham số.

Ví dụ:

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
    void inject(MyViewModel viewModel);
}

Trong ví dụ trên, AppComponent kết nối AppModule và cho phép inject SharedPreferences vào MyViewModel.

Kết luận

Bài viết này đã giới thiệu về Dependency Injection và Dagger 2. Hy vọng bạn đã có cái nhìn tổng quan về cách Dagger 2 giúp quản lý dependencies trong ứng dụng Android. Trong các bài viết tiếp theo, chúng ta sẽ tìm hiểu cách triển khai Dagger 2 vào một project Android nhỏ.

Ảnh minh họa Dagger 2Ảnh minh họa Dagger 2

Nguồn tham khảo: