Dagger Android là gì

Trong bài này, bạn sẽ học về tầm quan trọng của Dependency Injection (DI) để tạo một ứng dụng vững chắc và có thể mở rộng quy mô cho các dự án lớn. Chúng ta sẽ sử dụng Dagger như là một công cụ DI để quản lý các phụ thuộc.

Dependency Injection (DI) là một kỹ thuật được sử dụng rộng rãi trong lập trình và rất phù hợp cho việc phát triển Android. Với việc theo các nguyên tắc của DI, bạn đặt nền tảng cho một kiến trúc ứng dụng tốt.

Việc thực hiện DI cung cấp cho bạn các ưu điểm sau đây:

  • Tái sử dụng mã.
  • Dễ dàng cho việc tái cấu trúc.
  • Dễ dàng cho việc kiểm thử.

Nếu bạn gặp phải bất kỳ vấn đề nào ( lỗi mã, lỗi cú pháp, từ ngữ không rõ ràng,v.v..) khi bạn làm việc trong bài này, vui lòng gửi báo cáo lỗi thông qua link Report a mistake ở góc dưới bên trái của bài này.

Bạn nên biết trước những gì

  • Có kinh nghiệm với cú pháp Kotlin .
  • Bạn hiểu về Dependency Injection và biết về các lợi ích của việc sử dụng Dagger trong các ứng dụng Android.

Bạn sẽ học được gì

  • Cách sử dụng Dagger trong ứng dụng Android trên quy mô lớn.
  • Các khái niệm Dagger có liên quan để tạo ra một ứng dụng bền vững và chắc chắn hơn.
  • Tại sao phải cần các thành phần con Dagger và cách sử dụng chúng.
  • Cách kiểm thử ứng dụng của bạn bằng việc sử dụng Dagger với các bài kiểm tra thiết bị và theo các unit test.

Cuối bài, bạn sẽ có một sơ đồ ứng dụng được tạo và đã kiểm thử giống như dưới đây:

Dagger Android là gì

Mũi tên đại diện cho các phụ thuộc giữa các đối tượng. Điều này là những gì chúng ta gọi là application graph: tất cả các lớp của ứng dụng và các phụ thuộc giữa chúng.

Hãy tiếp tục đọc bài này và học cách làm điều đó!

Nhận mã nguồn

Nhận mã từ Github:

$ git clone https://github.com/googlecodelabs/android-dagger

Ngoài ra bạn có thể tải xuống nơi lưu trữ dưới một tệp zip:

Tải xuống Zip

Mở Android Studio

Nếu bạn cần tải xuống Android Studio, bạn có thể tải xuống ở đây.

Thiết lập dự án

Dự án được xây dựng trong nhiều nhánh trong Github:

  • master là nhánh bạn đã kiểm tra và tải xuống. Đây là điểm bắt đầu của bài này.
  • 1_registration_main, 2_subcomponents, và 3_dagger_app là các bước trung gian hướng tới giải pháp.
  • solution chứa giải pháp cho bài này.

Chúng tôi khuyên bạn nên thực hiện bài này theo từng bước theo tốc độ của riêng bạn bắt đầu từ nhánh master.

Trong suốt bài này, bạn sẽ được trình bày vài đoạn mã mà bạn phải thêm vào dự án. Ở một vài chỗ, bạn cũng phải xóa đi vài đoạn mã sẽ được đề cập cụ thể và comment một vài đoạn mã.

Giống như các điểm kiểm tra, bạn có sẵn vài nhánh trung gian hỗ trợ bạn trong vài trường hợp cụ thể.

Để nhận nhận nhánh solution, sử dụng git, hãy sử dụng lệnh dưới đây:

$ git clone -b solution https://github.com/googlecodelabs/android-dagger

Hoặc tải xuống mã giải pháp ở đây:

Tải xuống mã hoàn chỉnh

Các câu hỏi thường gặp

Cách cài đặt Android Studio?

Cách thiết lập thiết bị cho phát triển ứng dụng?

Đầu tiên, hãy xem ứng dụng mẫu trông như thế nào. Theo các bước hướng dẫn này để mở ứng dụng mẫu trong Android Studio.

  • Nếu bạn đã tải xuống tệp zip, giải nén tệp.
  • Mở dự án trên Android Studio.
  • Nhấn vào Button
    Dagger Android là gì
    , và chọn hoặc emulator hoặc kết nối với thiết bị Android. Màn hình đăng ký sẽ hiện ra.

Dagger Android là gì

Ứng dụng chứa 4 luồng khác nhau ( được thực hiện bởi các Activity):

  • Registration: Người dùng có thể đăng ký bằng việc nhập tên đăng nhập, mật khẩu và chấp nhận các điều khoản và điều kiện.
  • Login: Người dùng có thể đăng nhập sử dụng chứng thực nhập trong luồng đăng ký và cũng có thể hủy đăng ký từ ứng dụng.
  • Home: Người dùng được chào đón và có thể xem có bao nhiêu thông báo chưa được đọc.
  • Setting: Người dùng có thể đăng xuất và làm mới số thông báo chưa được đọc ( đó là việc tạo ra một số thông báo ngẫu nhiên).

Dự án dựa theo mô hình MVVM điển hình, trong đó tất cả độ phức tạp của View được chuyển sang ViewModel. Hãy dành chút thời gian để làm quen với cấu trúc của dự án.

Dagger Android là gì

Các mũi tên đại diện các phụ thuộc giữa các đối tượng. Đây là những gì chúng tôi gọi là application graph: tất cả các lớp của ứng dụng và các phụ thuộc giữa chúng.

Mã trong nhánh master quản lý các phụ thuộc theo cách thủ công. Thay vì tạo chúng bằng tay, chúng ta sẽ tái cấu trúc ứng dụng, bằng cách sử dụng Dagger để quản lý chúng cho chúng ta.

Từ chối trách nhiệm

Bài này không cố chấp trong cách mà bạn kiến trúc ứng dụng. Nó nhằm giới thiệu các cách khác nhau mà bạn có thể đưa Dagger vào trong kiến trúc ứng dụng của bạn: một Activity đơn với nhiều Fragment (luồng đăng ký và đăng nhập) hoặc nhiều Activity (luồng ứng dụng chính).

Hoàn thành bài này để hiểu về các khái niệm chính của Dagger để bạn có thể áp dụng chúng vào dự án của mình. Vài mô hình được sử dụng trong bài này không phải là cách được khuyến khích để xây dựng các ứng dụng Android, tuy nhiên, nó là cách tốt nhất để giải thích về Dagger.

Để tìm hiểu thêm về kiến trúc ứng dụng Android, hãy xem trang Guide to App architecture page.

Tại sao là Dagger?

Nếu ứng dụng ngày càng lớn, chúng ta sẽ bắt đầu viết nhiều mã soạn sẵn (ví dụ: Factory) có thể dễ xảy ra lỗi. Làm sai điều này có thể dẫn đến các lỗi nhỏ và rò rỉ bộ nhớ trong ứng dụng của bạn.

Trong bài này, bạn sẽ thấy cách sử dụng Dagger để tự động hóa quá trình này và sinh ra các mã giống như bạn đã viết bằng tay.

Dagger sẽ có trách nhiệm tạo một application graph cho chúng ta. Chúng ta cũng sẽ sử dụng Dagger để thực hiện việc đưa trường (field injection) vào các Activity, thay vì tạo các phụ thuộc bằng tay.

Thông tin thêm về tại sao sử dụng Dagger ở đây.

Để thêm Dagger vào dự án của bạn, mở tệp app/build.gradle và thêm hai dependency Dagger và plugin kapt ở đầu tệp.

app/build.gradle

apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' ... dependencies { ... def dagger_version = "2.27" implementation "com.google.dagger:dagger:$dagger_version" kapt "com.google.dagger:dagger-compiler:$dagger_version" }

Sau khi thêm những dòng này vào tệp, nhấn vào Button "Sync Now" hiện ra ở đầu tệp. Điều này sẽ đồng bộ lại dự án và tải xuống các dependency mới. Bây giờ chúng ta đã sẵn sàng sử dụng Dagger trong ứng dụng.

Dagger thực hiện bằng việc sử dụng mô hình các chú thích của Java. Nó sinh ra mã ở thời điểm biên dịch sử dụng annotation processor. Annotation processor được hỗ trợ trong Kotlin với plugin biên dịch kapt. Chúng được thiết lập bằng việc thêm apply plugin: kotlin-kapt' ở đầu tệp ngay dưới dòng apply plugin: kotlin-android-extensions'.

Trong các dependency, thư viện dagger chứa tất cả các chú thích bạn có thể sử dụng trong ứng dụng và dagger-compiler là annotation processor mà sẽ sinh ra mã cho chúng ta. Nó sẽ không đóng gói trong ứng dụng của bạn.

Bạn có thể tìm các phiên bản mới nhất có sẵn của Dagger ở đây.

Hãy bắt đầu tái cấu trúc luồng đăng ký để sử dụng Dagger.

Để xây dựng application graph tự động cho chúng ta, Dagger cần biết cách tạo các đối tượng cho các lớp trong biểu đồ. Một cách để làm việc này là bằng việc chú thích hàm khởi tạo của các lớp với @Inject. Các tham số hàm khởi tạo sẽ là các phụ thuộc của kiểu đó.

Mở tệp RegistrationViewModel.kt và thay thế việc định nghĩa lớp bằng định nghĩa dưới đây:

RegistrationViewModel.kt

// @Inject tells Dagger how to provide instances of this type // Dagger also knows that UserManager is a dependency class RegistrationViewModel @Inject constructor(val userManager: UserManager) { ... }

Trong Kotlin, để áp dụng một chú thích vào hàm khởi tạo, bạn cần thêm cụ thể từ khóa constructor và giới thiệu annotation ngay trước nó giống như đoạn mã ở trên.

Với chú thích @Inject, Dagger biết:

  1. Cách tạo các đối tượng của kiểu RegistrationViewModel.
  2. RegistrationViewModel có UserManager như một phụ thuộc bởi vì hàm khởi tạo giữ một đối tượng của UserManager như một đối số.

Dagger không biết cách tạo các đối tượng của UserManager nữa. Thực hiện theo cách tương tự, và thêm chú thích @Inject vào hàm khởi tạo UserManager.

Mở tệp UserManager.kt và thay thế định nghĩa lớp bằng định nghĩa dưới đây:

UserManager.kt

class UserManager @Inject constructor(private val storage: Storage) { ... }

Bây giờ, Dagger đã biết cách tạo các đối tượng của RegistrationViewModel và UserManager.

Bởi vì UserManager có phụ thuộc (đó là Storage) là một giao diện, chúng ta cần nói cho Dagger biết cách tạo một đối tượng của kiểu đó theo cách khác, chúng ta sẽ đề cập điều này sau.

Các View đòi hỏi các đối tượng từ graph

Một số lớp nhất định của framework Android như Activity và Fragment được khởi tạo bởi hệ thống vì vậy Dagger không thể tạo chúng cho bạn. Đối với các Activity cụ thể, bất kỳ mã khởi tạo nào cần nằm ở phương thức onCreate(). Bởi vì điều này, chúng ta không thể sử dụng chú thích @Inject trong hàm khởi tạo của lớp View như chúng ta đã làm ở trên ( Điều này được gọi là đưa vào hàm khởi tạo (constructor injection)). Thay vì vậy, chúng ta phải sử dụng việc đưa vào trường khởi tạo (field injection).

Thay vì tạo các phụ thuộc mà Activity đòi hỏi trong phương thức onCreate() như chúng ta làm với dependency injection theo cách thủ công, chúng ta muốn Dagger đưa các phụ thuộc đó vào cho chúng ta. Đối với field injection (điều này được sử dụng phổ biến trong các Activity và Fragment), chúng ta chú thích với @Inject các trường mà chúng ta muốn Dagger cung cấp.

Trong ứng dụng, RegistrationActivity có một phụ thuộc là RegistrationViewModel.

Nếu bạn mở RegistrationActivity.kt, chúng ta đang tạo ViewModel trong phương thức onCreate() ngay trước khi gọi supportFragmentManager. Chúng ta không muốn tạo nó bằng cách thủ công, chúng ta muốn Dagger cung cấp nó. Đối với việc này, chúng ta cần làm:

  • Chú thích trường với @Inject.
  • Loại bỏ việc khởi tạo trong phương thức onCreate().

RegistrationActivity.kt

class RegistrationActivity : AppCompatActivity() { // @Inject annotated fields will be provided by Dagger @Inject lateinit var registrationViewModel: RegistrationViewModel override fun onCreate(savedInstanceState: Bundle?) { ... // Remove following line registrationViewModel = RegistrationViewModel((application as MyApplication).userManager) } }

Làm thế nào chúng ta có thể nói cho Dagger biết các đối tượng nào cần được lồng vào RegistrationActivity? Chúng ta cần tạo một Dagger graph ( hoặc application graph) và sử dụng nó để lồng các đối tượng vào trong Activity.

Bạn muốn Dagger tạo một biểu đồ (graph) các phụ thuộc cho ứng dụng của chúng ta, quản lý chúng thay chúng ta và có thể nhận các phụ thuộc từ biểu đồ. Để Dagger làm được điều đó, chúng ta cần tạo một giao diện (interface) và chú thích nó với @Component. Dagger sẽ tạo một Container giống như chúng ta đã làm với dependency injection bằng cách thủ công.

Một giao diện được chú thích với @Component sẽ làm cho Dagger tạo các mã với tất cả các phụ thuộc cần thiết để đáp ứng các tham số của các phương thức mà nó có. Bên trong giao diện đó, chúng ta có thể nói cho Dagger rằng RegistrationActivity đòi hỏi việc đưa vào đối tượng.

Tạo mội gói mới, đặt tên là di trong gói com.example.android.dagger (cùng cấp với các gói khác như registration). Bên trong gói đó, tạo mới một tệp Kotlin, đặt tên là AppComponent.kt và định nghĩa một giao diện như chúng ta mô tả ở trên:

app/src/main/java/com/example/android/dagger/di/AppComponent.kt

package com.example.android.dagger.di import com.example.android.dagger.registration.RegistrationActivity import dagger.Component // Definition of a Dagger component @Component interface AppComponent { // Classes that can be injected by this Component fun inject(activity: RegistrationActivity) }

Với phương thức inject(activity: RegistrationActivity) trong giao diện @Component, chúng ta đang nói cho Dagger rằng RegistrationActivity yêu cầu việc đưa vào và nó phải cung cấp các phụ thuộc mà đã được chú thích với @Inject ( ví dụ RegistrationViewModel mà chúng ta đã định nghĩa ở bước trước đó).

Bởi vì Dagger phải tạo một đối tượng nội bộ RegistrationViewModel, nó cũng cần đáp ứng các phụ thuộc của RegistrationViewModel ( tức là UserManager). Nếu trong suốt quá trình đệ quy này để tìm các phụ thuộc, Dagger không biết cách cung cấp một phụ thuộc cụ thể, nó sẽ bị lỗi ở thời gian biên dịch, nói rằng ở đó có một phụ thuộc mà không thể đáp ứng.

Việc biên dịch ứng dụng kích hoạt annotation processor của Dagger mà sẽ tạo mã chúng ta cần cho việc quản lý các phụ thuộc. Nếu chúng ta thực hiện điều này bằng việc sử dụng Button biên dịch

Dagger Android là gì
trong Android Studio, chúng ta nhận được lỗi sau đây ( bạn có thể cần bật soft-wrap bằng việc sử dụng Button này
Dagger Android là gì
để xem lỗi dễ dàng hơn):

dagger/app/build/tmp/kapt3/stubs/debug/com/example/android/dagger/di/AppComponent.java:7: error: [Dagger/MissingBinding] com.example.android.dagger.storage.Storage cannot be provided without an @Provides-annotated method

Hãy cùng xem xét thông báo lỗi này. Đầu tiên, nó đang nói cho chúng ta rằng chúng ta gặp lỗi trong AppComponent. Kiểu lỗi này là [Dagger/MissingBinding], nghĩa là Dagger không biết cách cung cấp một kiểu nhất định. Nếu đọc kỹ, nó nói rằng Storage không thể được cung cấp mà không có phương thức chú thích @Provides.

Chúng ta chưa nói cho Dagger cách để cung cấp một đối tượng kiểu Storage mà được sử dụng bởi UserManager.

Cách mà chúng ta nói Dagger cung cấp đối tượng Storage là khác biệt bởi vì Storage là một giao diện và như vậy không thể khởi tạo trực tiếp. Chúng ta cần nói cho Dagger biết lớp thực hiện cho giao diện Storage mà chúng ta muốn sử dụng. Trong trường hợp này, nó là SharedPreferencesStorage.

Để làm điều này chúng ta sẽ sử dụng Dagger Module. Dagger Module là một lớp được chú thích với @Module.

Tương tự Component, Dagger Module nói cho Dagger biết cách cung cấp các đối tượng của một kiểu nhất định. Các phụ thuộc được định nghĩa sử dụng các chú thích @provides và @Binds.

Bởi vì Module này sẽ chứa thông tin về Storage, hãy tạo một tệp khác, đặt tên là StorageModule.kt trong cùng gói mà chúng ta đã tạo lớp AppComponent.kt. Trong tệp này, chúng ta định nghĩa một lớp, đặt tên là StorageModule và chú thích nó với @Module.

app/src/main/java/com/example/android/dagger/di/StorageModule.kt

package com.example.android.dagger.di import dagger.Module // Tells Dagger this is a Dagger module @Module class StorageModule { }

Chú thích @Binds

Sử dụng @Binds để nói Dagger biết nó cần sử dụng các thực hiện nào khi cung cấp một giao diện.

@Binds phải chú thích một hàm trừu tượng. Kiểu trả về của hàm trừu tượng là một giao diện mà chúng ta muốn cung cấp thực hiện cho (tức là Storage). Việc thực hiện được chỉ ra bằng việc thêm một tham số với kiểu thực hiện của giao diện (tức là SharedPreferencesStorage).

StorageModule.kt

// Tells Dagger this is a Dagger module // Because of @Binds, StorageModule needs to be an abstract class @Module abstract class StorageModule { // Makes Dagger provide SharedPreferencesStorage when a Storage type is requested @Binds abstract fun provideStorage(storage: SharedPreferencesStorage): Storage }

Với mã trên, chúng ta nói với Dagger "khi bạn cần một đối tượng Storage, hãy sử dụng SharedPreferencesStorage".

Vài lưu ý sau đây:

  • provideStorage chỉ là một tên phương thức tùy ý, nó có thể được đặt bất kỳ tên gì bạn thích, nó không quan trọng với Dagger. Những gì Dagger quan tâm là tham số và kiểu trả về.
  • StorageModule bây giờ cũng là abstract bởi vì phương thức provideStorage cũng là trừu tượng.

Chúng ta đã nói cho Dagger biết khi một đối tượng Storage được yêu cầu, nó cần tạo một đối tượng của lớp SharedPreferencesStorage, nhưng chúng ta chưa nói cho Dagger biết cách tạo các đối tượng của SharedPreferencesStorage với @Inject.

SharedPreferencesStorage.kt

// @Inject tells Dagger how to provide instances of this type class SharedPreferencesStorage @Inject constructor(context: Context) : Storage { ... }

Application graph cần biết về StorageModule. Cho việc này, chúng ta khai báo nó trong lớp AppComponent với tham số modules bên trong chú thích @Component, giống như dưới đây:

AppComponent.kt

// Definition of a Dagger component that adds info from the StorageModule to the graph @Component(modules = [StorageModule::class]) interface AppComponent { // Classes that can be injected by this Component fun inject(activity: RegistrationActivity) }

Trong cách này, AppComponent có thể truy cập thông tin mà StorageModule chứa. Trong một ứng dụng phức tạp hơn, chúng ta cũng có thể có một NetworkModule, chẳng hạn để thêm thông tin về cách cung cấp OkHttpClient hoặc cách cấu hình Gson hoặc Moshi.

Nếu chúng ta thử biên dịch lại lần nữa, chúng ta vẫn nhận một lỗi, rất giống với lỗi lần trước. Lần này, những gì Dagger không tìm thấy là: Context.

Chú thích @BindsInstance

Làm thế nào chúng ta có thể nói cho Dagger cách cung cấp một Context? Context được cung cấp bởi hệ thống Android và tóm lại nó được khởi tạo bên ngoài của biểu đồ. Bởi vì Context đã có sẵn ở thời điểm chúng ta đang tạo một đối tượng biểu đồ, chúng ta có thể truyền nó vào.

Cách để truyền nó vào là sử dụng chú thích @BindsInstance với một thành phần Factory.

AppComponent.kt

@Component(modules = [StorageModule::class]) interface AppComponent { // Factory to create instances of the AppComponent @Component.Factory interface Factory { // With @BindsInstance, the Context passed in will be available in the graph fun create(@BindsInstance context: Context): AppComponent } fun inject(activity: RegistrationActivity) }

Chúng ta đang khai báo một giao diện được chú thích với @Component.Factory. Bên trong, có một phương thức trả về một đối tượng kiểu component (tức là AppComponent) và có một tham số kiểu Context được chú thích với @BindsInstance.

@BindsInstance nói cho Dagger biết rằng nó cần thêm đối tượng đó vào trong biểu đồ và chỗ nào Context được yêu cầu, thì cung cấp cho đối tượng đó.

Dự án biên dịch thành công

Dự án bây giờ nên biên dịch không xảy ra lỗi nữa. Dagger đã sinh ra một application graph thành công và bạn đã sẵn sàng để sử dụng nó.

Việc triển khai cho application graph được tự động sinh ra mã bởi annotation processor. Lớp sinh ra đặt tên là Dagger{ComponentName} và chứa các mã thực thi cho biểu đồ (graph). Chúng ta sẽ sử dụng lớp được sinh ra DaggerAppComponent trong phần tiếp theo.

Biểu đồ AppComponent bây giờ trông như thế nào ?

Dagger Android là gì

AppComponent bao gồm StorageModule với thông tin về cách cung cấp các đối tượng Storage. Storage có một phụ thuộc Context nhưng bởi vì chúng ta đang cung cấp nó khi tạo ra biểu đồ, Storage đã có tất cả các phụ thuộc cần thiết.

Đối tượng Context được truyền vào trong phương thức create của Factory trong AppComponent. Do đó, chúng ta sẽ cung cấp cùng một đối tượng Context bất kỳ lúc nào. Nó được đại diện với dấu chấm trắng trong biểu đồ.

Bây giờ, RegistrationActivity có thể truy cập vào biểu đồ để nhận các đối tượng được cung cấp bởi Dagger, trong trường hợp này là RegistrationViewModel (bởi vì nó là trường mà được chú thích với @Inject).

Vì AppComponent cần cung cấp RegistrationViewModel cho RegistrationActivity, nó cần tạo một đối tượng RegistrationViewModel. Để làm điều này nó cần đáp ứng các phụ thuộc của RegistrationViewModel (tức là UserManager) và cũng tạo một đối tượng UserManager. Vì UserManager có hàm khởi tạo được chú thích với @Inject, Dagger sẽ sử dụng nó để tạo các đối tượng. UserManager có một phụ thuộc Storage nhưng bởi vì nó đã có sẵn trong biểu đồ, nên không cần thêm gì nữa.

Trong Android, bạn thường tạo một biểu đồ Dagger tồn tại trong lớp Application của bạn, bởi vì bạn muốn biểu đồ nằm trong bộ nhớ khi ứng dụng đang hoạt động. Theo cách này, biểu đồ được đính kèm với vòng đời của ứng dụng. Trong trường hợp chúng ta, chúng ta cũng muốn có một Context của ứng dụng nằm trong biểu đồ. Như một ưu điểm, biểu đồ dành cho các lớp của framework Android khác ( mà có thể truy cập với Context của chúng) và cũng là điều tốt cho việc kiểm thử bởi vì bạn có thể sử dụng một lớp Application tùy chọn trong việc kiểm tra.

Hãy thêm một đối tượng của biểu đồ (tức là AppComponent) vào lớp Application riêng của dự án: MyApplication.

MyApplication.kt

open class MyApplication : Application() { // Instance of the AppComponent that will be used by all the Activities in the project val appComponent: AppComponent by lazy { // Creates an instance of AppComponent using its Factory constructor // We pass the applicationContext that will be used as Context in the graph DaggerAppComponent.factory().create(applicationContext) } open val userManager by lazy { UserManager(SharedPreferencesStorage(this)) } }

Như đã đề cập ở phần trước, Dagger sinh ra một lớp, gọi là DaggerAppComponent chứa việc thực hiện của biểu đồ AppComponent khi chúng ta biên dịch dự án. Bởi vì chúng ta định nghĩa một thành phần Factory với chú thích @Component.Factory, chúng ta có thể gọi .factory(), là một phương thức static của DaggerAppComponent. Với điều đó, bây giờ chúng ta có thể gọi phương thức create() mà chúng ta định nghĩa bên trong Factory, ở nơi chúng ta truyền vào Context, trong trường hợp này là applicationContext.

Chúng ta làm điều đó bằng việc sử dụng Kotlin lazy initialization để biến là kiểu không thể thay đổi và chỉ được khởi tạo khi cần thiết.

Chúng ta có thể sử dụng đối tượng này của biểu đồ trong RegistrationActivity để làm cho Dagger đưa vào các trường được chú thích bằng @Inject. Làm thế nào có thể làm điều đó? Chúng ta phải gọi phương thức inject của AppComponent, đưa vào RegistrationActivity như một tham số.

Chúng ta cũng cần loại bỏ đoạn mã, khởi tạo các trường để không ghi đè lên những mã mà Dagger hiện đã khởi tạo cho chúng ta trong RegistrationActivity.kt

class RegistrationActivity : AppCompatActivity() { // @Inject annotated fields will be provided by Dagger @Inject lateinit var registrationViewModel: RegistrationViewModel override fun onCreate(savedInstanceState: Bundle?) { // Ask Dagger to inject our dependencies (application as MyApplication).appComponent.inject(this) super.onCreate(savedInstanceState) setContentView(R.layout.activity_registration) // REMOVE THIS LINE registrationViewModel = RegistrationViewModel((application as MyApplication).userManager) supportFragmentManager.beginTransaction() .add(R.id.fragment_holder, EnterDetailsFragment()) .commit() } ... }

Việc gọi phương thức appComponent.inject(this) để đưa trường mà RegistrationActivity đã chú thích với @Inject (tức là registrationViewModel).

RegistrationActivity đã sử dụng Dagger để quản lý các phụ thuộc! Bạn có thể chạy ứng dụng

Dagger Android là gì
.

Bạn có thấy lỗi xảy ra ? Màn hình chính nên hiện ra sau luồng đăng ký! Nhưng nó không hiện ra và hiện ra màn hình đăng nhập. Tại sao vậy? Các luồng khác của ứng dụng hiện không sử dụng biểu đồ Dagger.

MainActivity vẫn đang sử dụng userManager được định nghĩa trong MyApplication trong khi luồng đăng ký đã sử dụng một đối tượng userManager từ biểu đồ Dagger.

Hãy sử dụng Dagger trong luồng chính để sửa vấn đề này.

Sử dụng Dagger trong luồng chính

Giống như trước đó, bạn muốn MainActivity sử dụng Dagger để quản lý các phụ thuộc. Trong trường hợp này, MainViewModel và UserManager.

Để nói cho Dagger biết rằng MainActivity đòi hỏi việc được lồng đối tượng vào, chúng ta phải thêm một hàm khác với MainActivity là một tham số trong giao diện AppComponent.

AppComponent.kt

@Component(modules = [StorageModule::class]) interface AppComponent { ... // Classes that can be injected by this Component fun inject(activity: RegistrationActivity) fun inject(activity: MainActivity) }

Tên của hàm không quan trọng ( đó là lý do vì sao chúng ta gọi cả hai là hàm inject), những gì quan trọng là kiểu tham số. Hãy định nghĩa những gì bạn muốn đưa vào từ Dagger trong MainActivity và đưa vào trong biểu đồ:

  1. Thêm userManager như một biên thành viên của lớp (thay vì biến cục bộ trong phương thức onCreate()) và đưa các trường userManager và mainViewModel với @Inject.

MainActivity.kt

class MainActivity : AppCompatActivity() { // @Inject annotated fields will be provided by Dagger @Inject private lateinit var userManager: UserManager @Inject private lateinit var mainViewModel: MainViewModel ... }
  1. Xóa việc khởi tạo userManager và mainViewModel bởi vì điều đó đã được hoàn tất bởi Dagger.

MainActivity.kt

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { ... // Remove this line userManager = (application as MyApplication).userManager if (!userManager.isUserLoggedIn()) { ... } else { ... // Remove this line too mainViewModel = MainViewModel(userManager.userDataRepository!!) ... } } ... }
  1. Gọi phương thức inject trong appComponent để đưa các trường được lồng vào.

MainActivity.kt

class MainActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { (application as MyApplication).appComponent.inject(this) super.onCreate(savedInstanceState) ... } }

UserManager đã có sẵn trong biểu đồ vì vậy Dagger biết cách để cung cấp nó nhưng MainViewModel thì không. Hãy thêm chú thích @Inject vào hàm khởi tạo để Dagger biết cách tạo đối tượng của lớp.

MainViewModel.kt

class MainViewModel @Inject constructor(private val userDataRepository: UserDataRepository) { ... }

Bởi vì MainViewModel có một phụ thuộc là UserDataRepository, chúng ta cũng phải chú thích nó với @Inject.

UserDataRepository.kt

class UserDataRepository @Inject constructor(private val userManager: UserManager) { ... }

Bởi vì UserManager đã là một phần trong biểu đồ, Dagger đã có đủ tất cả thông tin nó cần để xây dựng một biểu đồ thành công.

Dagger Android là gì

Trạng thái hiện tại của biểu đồ ứng dụng (application graph) ở thời điểm này.

Nếu bạn thử biên dịch lại ứng dụng, bạn sẽ nhận được lỗi khác.

Nó nói lỗi gì ? error: Dagger does not support injection into private fields. Đó là một trong các hạn chế của Dagger, các trường được lồng vào cần ít nhất được khả dụng trong gói của nó hoặc cao hơn. Chúng không thể là private trong lớp riêng của nó.

Loại bỏ từ khóa private ở định nghĩa các trường.

MainActivity.kt

class MainActivity : AppCompatActivity() { @Inject lateinit var userManager: UserManager @Inject lateinit var mainViewModel: MainViewModel ... }

Ứng dụng bây giờ có thể được biên dịch thành công. Hãy chạy

Dagger Android là gì
ứng dụng lần nữa. Khi bạn chạy ứng dụng, bởi vì chúng ta đã đăng ký người dùng trước đó, bạn sẽ được hiển thị màn hình đăng nhập. Để bắt đầu mới hoàn toàn như ở lần đầu tiên, nhấn vào "Unregister" để đi đến màn hình đăng ký.

Khi bạn đăng ký xong, nó không vào màn hình chính. Nó lại đi vào màn hình đăng nhập lần nữa. Lỗi vẫn còn xảy ra, nhưng tại sao ? Cả luồng chính và đăng ký để cùng sử dụng UserManager từ biểu đồ ứng dụng (application graph).

Vấn đề ở đây là Dagger luôn luôn cung cấp một đối tượng mới của một kiểu (trong trường hợp chúng ta là UserManager) khi đưa vào các phụ thuộc theo mặc định. Làm thế nào để Dagger sử dụng lại cùng một đối tượng mọi lúc? Hãy sử dụng Scopes.

Thỉnh thoảng, bạn có thể muốn cung cấp cùng một đối tượng phụ thuộc trong một thành phần Component cho nhiều lý do:

  1. Bạn muốn các kiểu khác cùng có một kiểu phụ thuộc để chia sẻ cùng một đối tượng (tức là UserManager trong trường hợp của chúng ta).
  2. Một đối tượng rất tốn chi phí cho việc tạo và bạn không muốn tạo mới một đối tượng mỗi lần nó được khai báo như một phụ thuộc (tức là một trình phân tích Json).

Sử dụng Scopes để muốn có một đối tượng của một kiểu duy nhất trong một Component. Điều này là những gì còn được gọi là "để xác định phạm vi một kiểu trong vòng đời của Component". Xác định phạm vi một kiểu trong một Component nghĩa là cùng một đối tượng của kiểu đó sẽ được sử dụng mọi lúc khi được cung cấp.

Đối với AppComponent, chúng ta có thể sử dụng chú thích scope @Singleton, đó là một chú thích phạm vi duy nhất được đề cập trong gói javax.inject. Nếu bạn chú thích Component với @Singleton, tất cả các lớp mà cũng được chú thích với @Singleton sẽ được xác định phạm vi trong vòng đời của nó.

Mở tệp AppComponent.kt và chú thích Component với @Singleton.

AppComponent.kt

@Singleton @Component(modules = [StorageModule::class]) interface AppComponent { ... }

Bây giờ, các lớp được chú thích với @Singleton sẽ được xác định phạm vi trong AppComponent. Hãy chú thích UserManager để có một đối tượng duy nhất của nó trong biểu đồ ứng dụng (application graph).

UserManager.kt

@Singleton class UserManager @Inject constructor(private val storage: Storage) { ... }

Bây giờ, cùng một đối tượng giống nhau của UserManager sẽ được cung cấp cho cả RegistrationActivity và MainActivity.

Chạy

Dagger Android là gì
ứng dụng lần nữa và đi đến luồng đăng ký để bắt đầu từ đầu như đã làm trước đó. Bây giờ, khi bạn hoàn thành việc đăng ký, nó sẽ đi đến màn hình chính! Vấn đề đã được giải quyết.

Đây là những gì biểu đồ ứng dụng bây giờ trông giống như hình dưới đây:

Dagger Android là gì

Trạng thái hiện tại của biểu đồ với một đối tượng duy nhất của UserManager trong AppComponent

Chú ý rằng UserManager cũng được đánh dấu với dấu chấm trắng. Giống như với Context, cùng một đối tượng giống nhau UserManager sẽ được cung cấp khi được yêu cầu phụ thuộc trong cùng một đối tượng AppComponent.

Registration Flow

Hãy tiếp tục tái cấu trúc ứng dụng với Dagger. Các Fragment của luồng đăng ký vẫn đang sử dụng dependency injection bằng thủ công, bây giờ hãy chuyển chúng vào Dagger.

Bởi vì bạn muốn cả EnterDetailsFragment và TermsAndConditionsFragment đều được đưa vào các đối tượng bởi Dagger, chúng ta cần cho Dagger biết bằng việc thêm chúng vào giao diện AppComponent:

AppComponent.kt

@Singleton @Component(modules = [StorageModule::class]) interface AppComponent { ... fun inject(activity: RegistrationActivity) fun inject(fragment: EnterDetailsFragment) fun inject(fragment: TermsAndConditionsFragment) fun inject(activity: MainActivity) }

Những trường nào chúng ta muốn Dagger cung cấp? Trong EnterDetailsFragment, chúng ta muốn Dagger đưa vào cả hai ViewModel. Bạn làm điều đó bằng việc chú thích các trường với @Inject và loại bỏ từ khóa private.

EnterDetailsFragment.kt

class EnterDetailsFragment : Fragment() { @Inject lateinit var registrationViewModel: RegistrationViewModel @Inject lateinit var enterDetailsViewModel: EnterDetailsViewModel ... }

Nhưng bạn cũng cần loại bỏ việc khởi tạo bằng thủ công chúng ta có trong dự án. Hãy xóa các dòng sau:

EnterDetailsFragment.kt

class EnterDetailsFragment : Fragment() { override fun onCreateView(...): View? { ... // Remove following lines registrationViewModel = (activity as RegistrationActivity).registrationViewModel enterDetailsViewModel = EnterDetailsViewModel() ... } }

Bây giờ, bạn có thể sử dụng đối tượng appComponent trong lớp Application để đưa vào các Fragment. Đối với các Fragment, chúng ta đưa vào Component, sử dụng phương thức onAttach, và sau khi gọi phương thức super.onAttach().

EnterDetailsFragment.kt

class EnterDetailsFragment : Fragment() { override fun onAttach(context: Context) { super.onAttach(context) (requireActivity().application as MyApplication).appComponent.inject(this) } }

Những gì còn lại để Dagger biết cách cung cấp các đối tượng của EnterDetailsViewModel. Bạn làm điều đó bằng cách chú thích trong hàm khởi tạo với @Inject. RegistrationViewModel đã được chú thích với @Inject, vì RegistationActivity đã cần nó trước đó.

EnterDetailsViewModel.kt

class EnterDetailsViewModel @Inject constructor() { ... }

EnterDetailsFragment đã sẵn sàng, bây giờ chúng ta phải làm tương tự cho TermsAndConditionsFragment:

  1. Chú thích với @Inject các trường chúng ta muốn Dagger cung cấp (tức là registrationViewModel) và xóa đi từ khóa private.
  2. Loại bỏ việc khởi tạo registrationViewModel mà chúng ta cần cho dependency injection bằng thủ công.
  3. Đưa Dagger vào trong phương thức onAttach().

TermsAndConditionsFragment.kt

class TermsAndConditionsFragment : Fragment() { @Inject lateinit var registrationViewModel: RegistrationViewModel override fun onAttach(context: Context) { super.onAttach(context) (requireActivity().application as MyApplication).appComponent.inject(this) } override fun onCreateView(...): View? { ... // Remove following line registrationViewModel = (activity as RegistrationActivity).registrationViewModel ... } }

Bây giờ bạn có thể chạy

Dagger Android là gì
ứng dụng.

Chuyện gì xảy ra? Ứng dụng bị sự cố crash sau khi đăng ký. Vấn đề là các đối tượng khác nhau của RegistrationViewModel đã được đưa vào RegistrationActivity, EnterDetailsFragment, và TermsAndConditionsFragment. Tuy nhiên, đó không phải là điều chúng ta muốn. Chúng ta muốn cùng một đối tượng được đưa vào Activity và các Fragments.

Điều gì xảy ra nếu chúng ta chú thích RegistrationViewModel với @Singleton? Nó sẽ giải quyết được vấn đề bây giờ nhưng nó sẽ sinh ra các vấn đề khác trong tương lai:

  • Chúng ta có thể không muốn một đối tượng RegistrationViewModel luôn nằm trong bộ nhớ sau khi luồng đăng ký đã kết thúc.
  • Chúng ta muốn các đối tượng khác nhau của RegistrationViewModel cho các luồng đăng ký khác. Nếu người dùng đăng ký và hủy đăng ký, chúng ta không muốn dữ liệu từ việc đăng ký trước được hiện diện.

Chúng ta muốn các Fragment đăng ký được sử dụng lại cùng một ViewModel đến từ Activity, nhưng nếu Activity thay đổi, chúng ta muốn một đối tượng khác. Chúng ta cần xác định một phạm vi của RegistrationViewModel trong RegistrationActivity, cho điều đó, chúng ta cần tạo một Component mới cho luồng đăng ký và xác định phạm vi ViewModel trong thành phần Component đăng ký mới đó.

Để làm điều này chúng ta sử dụng Dagger subcomponents.

Dagger Subcomponents

Một RegistrationComponent có thể truy cập các đối tượng từ AppComponent bởi vì RegistrationViewModel có phụ thuộc là UserRepository. Cách để nói cho Dagger biết rằng chúng ta cần một component mới để sử dụng một phần của Component khác là sử dụng Dagger Subcomponents. Component mới (tức là RegistrationComponent) phải là một subcomponent của một Component đang chứa các tài nguyên cần chia sẻ (tức là AppComponent).

Bởi vì điều này dành riêng cho chức năng đăng ký, tạo mới một tệp đặt tên là RegistrationComponent.kt ben trong gói registration. Ở đây bạn có thể tạo một giao diện đặt tên là RegistrationComponent được chú thích với @Subcomponent để nói cho Dagger biết rằng đây là một thành phần con.

registration/RegistrationComponent.kt

package com.example.android.dagger.registration import dagger.Subcomponent // Definition of a Dagger subcomponent @Subcomponent interface RegistrationComponent { }

Thành phần này cần chứa thông tin đăng ký cụ thể. Để làm điều đó, chúng ta cần:

  1. Thêm các phương thức lồng vào từ AppComponent dành cho việc đăng ký, tức là RegistrationActivity, EnterDetailsFragment, và TermsAndConditionsFragment.
  2. Tạo một Factory Subcomponent để chúng ta có thể sử dụng tạo các đối tượng của subcomponent này.

RegistrationComponent.kt

// Definition of a Dagger subcomponent @Subcomponent interface RegistrationComponent { // Factory to create instances of RegistrationComponent @Subcomponent.Factory interface Factory { fun create(): RegistrationComponent } // Classes that can be injected by this Component fun inject(activity: RegistrationActivity) fun inject(fragment: EnterDetailsFragment) fun inject(fragment: TermsAndConditionsFragment) }

Trong AppComponent, chúng ta phải loại bỏ các phương thức có thể lồng vào các lớp cho đăng ký bởi vì sẽ không sử dụng nữa, những lớp này sẽ sử dụng RegistrationComponent.

Thay vào đó, RegistrationActivity tạo các đối tượng RegistrationComponent, chúng ta cần trình bày Factory trong giao diện AppComponent.

AppComponent.kt

@Singleton @Component(modules = [StorageModule::class]) interface AppComponent { @Component.Factory interface Factory { fun create(@BindsInstance context: Context): AppComponent } // Expose RegistrationComponent factory from the graph fun registrationComponent(): RegistrationComponent.Factory fun inject(activity: MainActivity) }

Chúng ta trình bày một Factory RegistrationComponent bằng việc khác báo một hàm với kiểu trả về là lớp đó.

Bây giờ, bạn phải làm cho AppComponent biết rằng RegistrationComponent là subcomponent của nó để nó có thể sinh ra mã phù hợp. Chúng ta cần tạo một Module Dagger để làm điều này.

Hãy tạo mới một tệp, đặt tên là AppSubcomponent.kt trong gói di. Trong tệp đó, chúng ta định nghĩa một lớp gọi là AppSubcomponents được chú thích với @Module. Để chỉ ra thông tin về subcomponent, chúng ta thêm vào một danh sách tên lớp thành phần trong biến subcomponents trong chú thích giống như dưới đây:

app/src/main/java/com/example/android/dagger/di/AppSubcomponents.kt

// This module tells AppComponent which are its subcomponents @Module(subcomponents = [RegistrationComponent::class]) class AppSubcomponents

Module mới này cũng cần được khai báo trong AppComponent:

AppComponent.kt

@Singleton @Component(modules = [StorageModule::class, AppSubcomponents::class]) interface AppComponent { ... }

Bây giờ AppComponent đã nhận biết được RegistrationComponent là một subcomponent của nó.

Bây giờ application graph trông thế nào?

Dagger Android là gì

Các lớp View được đưa vào các đối tượng đăng ký, bởi RegistrationComponent. Bởi vì RegistrationViewModel và EnterDetailsViewModel chỉ được yêu cầu từ các lớp sử dụng RegistrationComponent, chúng là một phần của subcomponent, không phải một phần của AppComponent.

Chúng ta đã tạo một subcomponent bởi vì chúng ta cần chia sẻ cùng một đối tượng RegistrationViewModel giữa Activity và các Fragment. Giống như chúng ta đã làm trước đó, nếu chúng ta chú thích Component và các lớp với cùng chú thích xác định phạm vi, điều đó tạo ra một đối tượng duy nhất trong Component.

Tuy nhiên, bạn không thể sử dụng @Singleton bởi vì nó đã được sử dụng bởi AppComponent. Chúng ta cần tạo một cái khác.

Trong trường hợp này, chúng ta có thể gọi phạm vi này là @RegistrationScope nhưng đây không phải là phương pháp tốt. Tên chú thích phạm vi không nên quá rõ ràng cho mục đích mà nó đáp ứng. Nó nên được đặt tên phụ thuộc vào vòng đời tồn tại vì các chú thích có thể được sử dụng lại bởi các thành phần anh em (tức là LoginComponent, SettingsComponent, etc). Đó là lý do thay vì gọi nó là @RegistrationScope, chúng ta gọi nó là @ActivityScope.

Hãy tạo một tệp đặt tên là ActivityScope.kt trong gói di và thêm việc định nghĩa ActivityScope như dưới đây:

app/src/main/java/com/example/android/dagger/di/ActivityScope.kt

@Scope @MustBeDocumented @Retention(value = AnnotationRetention.RUNTIME) annotation class ActivityScope

Để xác định phạm vi RegistrationViewModel trong RegistrationComponent, chúng ta phải chú thích cả lớp và giao diện với @ActivityScope.

RegistrationViewModel.kt

// Classes annotated with @ActivityScope will have a unique instance in this Component @ActivityScope @Subcomponent interface RegistrationComponent { ... }

RegistrationComponent.kt

// Classes annotated with @ActivityScope will have a unique instance in this Component @ActivityScope @Subcomponent interface RegistrationComponent { ... }

Bây giờ RegistrationComponent sẽ luôn luôn cung cấp cùng một đối tượng RegistrationViewModel.

Vòng đời của Subcomponent

AppComponent được đính kèm với vòng đời của Application bởi vì chúng ta muốn sử dụng một đối tượng giống nhau của biểu đồ cùng với Application trong bộ nhớ.

Vòng đời của RegistrationComponent là gì ? Một trong các lý do tại sao chúng ta cần điều này là bởi vì chúng ta muốn chia sẻ cùng một đối tượng giống nhau của RegistrationViewModel giữa các Activity và các Fragment. Nhưng chúng ta cũng muốn một đối tượng mới của RegistrationViewModel bất kỳ khi nào có luồng đăng ký mới.

RegistrationActivity là vòng đời chính xác cho RegistrationComponent: Cho mỗi Activity mới, chúng ta sẽ tạo một RegistrationComponent mới và các Fragment có thể sử dụng đối tượng RegistrationComponent đó.

Bởi vì RegistrationComponent được đính kèm vào vòng đời của RegistrationActivity, chúng ta phải giữ một tham chiếu đến Component trong Activity, giống như cách mà chúng ta giữ một tham chiếu đến appComponent trong lớp Application.

Trong cách này, các Fragment sẽ có thể truy cập nó.

RegistrationActivity.kt

class RegistrationActivity : AppCompatActivity() { // Stores an instance of RegistrationComponent so that its Fragments can access it lateinit var registrationComponent: RegistrationComponent ... }

Tạo mới một đối tượng RegistrationComponent trong phương thức onCreate() trước khi gọi super.onCreate và đưa vào registrationComponent, thay vì đưa vào Activity vào appComponent:

  1. Tạo mới một đối tượng RegistrationComponent bằng việc nhận một Factory từ appComponent và gọi phương thức create. Điều này là cho phép bởi vì chúng ta đã trình bày một hàm registrationComponent trong giao diện AppComponent để trả về một đối tượng Factory của RegistrationComponent.
  2. Gán đối tượng đó cho biến registrationComponent của Activity.
  3. Đưa Activity vào trong registrationComponent vừa được tạo để thực hiện việc đưa đối tượng vào các trường được chú thích với @Inject.

RegistrationActivity.kt

class RegistrationActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { // Remove lines (application as MyApplication).appComponent.inject(this) // Add these lines // Creates an instance of Registration component by grabbing the factory from the app graph registrationComponent = (application as MyApplication).appComponent.registrationComponent().create() // Injects this activity to the just created registration component registrationComponent.inject(this) super.onCreate(savedInstanceState) ... } ... }

registrationComponent đã có sẵn trong RegistrationActivity và chúng ta có thể sử dụng đối tượng đó để đưa vào các Fragment đăng ký. Trong phương thức onAttach trong các Fragment, sử dụng registrationComponent từ Activity:

EnterDetailsFragment.kt

class EnterDetailsFragment : Fragment() { ... override fun onAttach(context: Context) { super.onAttach(context) (activity as RegistrationActivity).registrationComponent.inject(this) } ... }

Và làm tương tự cho Fragment TermsAndConditionsFragment:

TermsAndConditionsFragment.kt

class TermsAndConditionsFragment : Fragment() { ... override fun onAttach(context: Context) { super.onAttach(context) (activity as RegistrationActivity).registrationComponent.inject(this) } }

Nếu bạn chạy

Dagger Android là gì
ứng dụng lần nữa và đi đến luồng đăng ký để bắt đầu lại từ đầu, như chúng ta đã làm trước đó, bạn có thể thấy luồng đăng ký làm việc như mong muốn. Lưu ý rằng màn hình Setting sẽ gây cho ứng dụng sự cố crash bởi vì nó chưa được tái cấu trúc sử dụng Dagger. Chúng ta sẽ làm điều đó sau.

Biểu đồ ứng dụng bây giờ trông như dưới đây:

Dagger Android là gì

Sự khác biệt với sơ đồ trước là việc RegistrationViewModel đã được xác định phạm vi trong RegistrationComponent, chúng ta trình bày điều đó bởi dấu màu cam trên RegistrationViewModel.

Ngoài việc xác định phạm vi các đối tượng đến vòng đời khác, tạo một subcomponent cũng là một phương pháp tốt để đóng gói các phần khác nhau theo từng cái trong ứng dụng.

Việc cấu trúc ứng dụng để tạo ra các biểu đồ con Dagger khác nhau phụ thuộc vào luồng ứng dụng giúp hướng tới một ứng dụng hiệu suất và có thể mở rộng cao hơn về bộ nhớ và thời gian tải ứng dụng. Tránh việc tạo một thành phần nguyên khối cung cấp mọi đối tượng trong ứng dụng, vì điều này làm cho các thành phần Dagger khó đọc và mô đun hóa.

Hãy tái cấu trúc luồng đăng nhập sử dụng Dagger bằng việc tạo mốt subcomponent khác dành cho luồng đăng nhập.

Tạo mới một tệp, đặt tên là LoginComponent.kt trong gói login và thêm định nghĩa LoginComponent như chúng ta đã làm với RegistrationComponent nhưng lúc này, với các lớp liên quan đến đăng nhập.

login/LoginComponent.kt

// Scope annotation that the LoginComponent uses // Classes annotated with @ActivityScope will have a unique instance in this Component @ActivityScope // Definition of a Dagger subcomponent @Subcomponent interface LoginComponent { // Factory to create instances of LoginComponent @Subcomponent.Factory interface Factory { fun create(): LoginComponent } // Classes that can be injected by this Component fun inject(activity: LoginActivity) }

Chúng ta có thể chú thích LoginComponent với ActivityScope bởi vì thành phần này sẽ có cùng vòng đời với LoginActivity.

Để nói cho Dagger biết cách tạo các đối tượng LoginViewModel, chúng ta chú thích hàm khởi tạo với chú thích @Inject.

LoginViewModel.kt

class LoginViewModel @Inject constructor(private val userManager: UserManager) { ... }

Trong trường hợp này, LoginViewModel không cần sử dụng lại bởi các lớp khác, đó là lý do bạn không nên chú thích nó với @ActivityScope.

Bạn cũng phải thêm một Subcomponent mới vào danh sách các Subcomponent của AppComponent trong Module AppSubcomponents.

AppSubcomponents.kt

@Module(subcomponents = [RegistrationComponent::class, LoginComponent::class]) class AppSubcomponents

Với LoginActivity, có thể truy cập vào Factory LoginComponent, bạn phải trình bày nó trong giao diện AppComponent.

AppComponent.kt

@Singleton @Component(modules = [StorageModule::class, AppSubcomponents::class]) interface AppComponent { ... // Types that can be retrieved from the graph fun registrationComponent(): RegistrationComponent.Factory fun loginComponent(): LoginComponent.Factory // Classes that can be injected by this Component fun inject(activity: MainActivity) }

Mọi thứ đã sẵn sàng để tạo các đối tượng của LoginComponent và đưa nó vào trong LoginActivity:

  1. Chú thích trường loginViewModel với chú thích @Inject bởi vì chúng ta muốn cung cấp bởi Dagger và loại bỏ từ khóa private.
  2. Nhận một Factory LoginComponent từ appComponent, bằng việc gọi phương thức loginComponent(), tạo một đối tượng LoginComponent với phương thức create() và gọi phương thức inject của thành phần truyền vào Activity.
  3. Loại bỏ việc khởi tạo loginViewModel từ việc thực hiện dependency injection bằng thủ công trước đó.

LoginActivity.kt

class LoginActivity : AppCompatActivity() { // 1) LoginViewModel is provided by Dagger @Inject lateinit var loginViewModel: LoginViewModel ... override fun onCreate(savedInstanceState: Bundle?) { // 2) Creates an instance of Login component by grabbing the factory from the app graph // and injects this activity to that Component (application as MyApplication).appComponent.loginComponent().create().inject(this) super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) // 3) Remove instantiation loginViewModel = LoginViewModel((application as MyApplication).userManager) ... } }

Nếu bạn chạy ứng dụng lần nữa, luồng đăng nhập bây giờ sẽ làm việc bình thường.

Với LoginComponent mới, biểu đồ ứng dụng bây giờ trông như dưới đây:

Dagger Android là gì

Nhấn vào Button Setting sẽ gây cho ứng dụng sự cố crash. Hãy sửa lỗi đó bằng việc tái cấu trúc mã Setting, sử dụng Dagger.

Bởi vì chúng ta muốn các trường trong SettingsActivity được đưa vào bởi Dagger:

  1. Nói Dagger cách tạo các đối tượng của các phụ thuộc trong SettingsActivity (tức là SettingsViewModel) bằng việc chú thích hàm khởi tạo với @Inject. Dagger đã biết cách tạo các đối tượng của các phụ thuộc trong SettingsViewModel.

SettingsViewModel.kt

class SettingsViewModel @Inject constructor( private val userDataRepository: UserDataRepository, private val userManager: UserManager ) { ... }
  1. Cho phép SettingsActivity được lồng các đối tượng vào bởi Dagger, bằng việc thêm hàm, nắm SettingsActivity như một tham số trong giao diện AppComponent.

AppComponent.kt

@Singleton @Component(modules = [StorageModule::class, AppSubcomponents::class]) interface AppComponent { ... fun inject(activity: SettingsActivity) }
  1. Trong lớp SettingsActivity, chú thích các trường được đưa vào với @Inject và bỏ đi từ khóa private.
  2. Truy cập appComponent từ MyApplication, gọi phương thức inject(this) để đưa trường với @Inject vào.
  3. Loại bỏ các khởi tạo bằng việc thực hiện dependency injection bằng thủ công.

SettingsActivity.kt

class SettingsActivity : AppCompatActivity() { // 1) SettingsViewModel is provided by Dagger @Inject lateinit var settingsViewModel: SettingsViewModel override fun onCreate(savedInstanceState: Bundle?) { // 2) Injects appComponent (application as MyApplication).appComponent.inject(this) super.onCreate(savedInstanceState) setContentView(R.layout.activity_settings) // 3) Remove following lines val userManager = (application as MyApplication).userManager settingsViewModel = SettingsViewModel(userManager.userDataRepository!!, userManager) ... } }

Nếu bạn chạy

Dagger Android là gì
ứng dụng, bạn có thể thấy rằng tính năng làm mới thông báo trong màn hình Settings không làm việc. Đó là bởi vì chúng ta không sử dụng lại cùng một đối tượng giống nhau của UserDataRepository trong MainActivity và SettingsActivity.

Chúng ta có thể xác định phạm vi UserDataRepository trong AppComponent bằng việc chú thích nó với @Singleton? Theo nguyên nhân giống như trước đây, chúng ta không muốn làm điều này bởi vì nếu người dùng đăng xuất hoặc hủy đăng ký, chúng ta không muốn giữ cùng một đối tượng giống nhau của UserDataRepository trong bộ nhớ. Dữ liệu đó dành riêng cho người dùng đã đăng nhập.

Chúng ta muốn tạo một Component mà nó tồn tại trong thời gian người dùng đăng nhập. Tất cả các Activity có thể truy cập sau khi người dùng đăng nhập, sẽ được lồng vào các đối tượng bởi Component này (tức là MainActivity và SettingsActivity).

Hãy tạo một subcomponent khác mà chúng ta có thể gọi là UserComponent như chúng ta đã thực hiện với LoginComponent và RegistrationComponent:

  1. Tạo một tệp Kotlin, đặt tên là UserComponent.kt trong thư mục user.
  2. Tạo một giao diện, đặt tên là UserComponent.kt được chú thích với @Subcomponent mà có thể đưa vào các lớp xảy ra sau khi người dùng đăng nhập và có một Factory.

app/src/main/java/com/example/android/dagger/user/UserComponent.kt

// Definition of a Dagger subcomponent @Subcomponent interface UserComponent { // Factory to create instances of UserComponent @Subcomponent.Factory interface Factory { fun create(): UserComponent } // Classes that can be injected by this Component fun inject(activity: MainActivity) fun inject(activity: SettingsActivity) }
  1. Thêm subcomponent mới này vào danh sách các subcomponent của AppComponent trong tệp AppSubcomponents.kt

AppSubcomponents.kt

@Module(subcomponents = [RegistrationComponent::class, LoginComponent::class, UserComponent::class]) class AppSubcomponents

Điều gì phụ trách vòng đời của UserComponent? LoginComponent và RegistrationComponent được quản lý bởi các Activity của nó nhưng UserComponent có thể đưa vào nhiều hơn một Activity và số lượng Activity có thể tăng lên.

Chúng ta phải đính kèm vòng đời của Component này vào điều gì biết được rằng khi nào người dùng đăng nhập và đăng xuất. Trong trường hợp của chúng ta, đó là UserManager. Nó xử lý việc đăng ký, đăng nhập và đăng xuất, vì vậy nó phù hợp cho đối tượng UserComponent ở đó.

Nếu UserManager cần tạo một đối tượng mới UserComponent, nó cần truy cập Factory của UserComponent. Nếu chúng ta thêm một Factory như là một tham số khởi tạo, Dagger sẽ cung cấp nó khi tạo một đối tượng của UserManager.

UserManager.kt

@Singleton class UserManager @Inject constructor( private val storage: Storage, // Since UserManager will be in charge of managing the UserComponent lifecycle, // it needs to know how to create instances of it private val userComponentFactory: UserComponent.Factory ) { ... }

Trong dependency injection bằng phương pháp thủ công, chúng ta có dữ liệu phiên đăng nhập của người dùng được lưu trong UserManager. Điều đó quyết định khi nào người đăng nhập hay không. Chúng ta có thể làm điều tương tự với UserComponent thay thế.

Chúng ta có thể giữ một đối tượng UserComponent trong UserManager để quản lý vòng đời của nó. Người dùng đã được đăng nhập nếu UserComponent khác null. Khi người dùng đăng xuất, chúng ta có thể loại bỏ đối tượng của UserComponent. Với cách này, bởi vì UserComponent chứa tất cả dữ liệu và các đối tượng của các lớp liên quan đến người dùng cụ thể, khi người dùng đăng xuất, khi chúng ta hủy bỏ Component, tất cả dữ liệu cũng sẽ được xóa trong bộ nhớ.

Thay đổi UserManager để sử dụng một đối tượng UserComponent thay vì UserDataRepository.

UserManager.kt

@Singleton class UserManager @Inject constructor(...) { //Remove line var userDataRepository: UserDataRepository? = null // Add or edit the following lines var userComponent: UserComponent? = null private set fun isUserLoggedIn() = userComponent != null fun logout() { userComponent = null } private fun userJustLoggedIn() { userComponent = userComponentFactory.create() } }

Như bạn thấy trong mã trên, chúng ta tạo một đối tượng userComponent khi người dùng đăng nhập, sử dụng phương thức create của Factory UserComponent. Và chúng ta loại bỏ đối tượng này khi phương thức logout() được gọi.

Chúng ta muốn UserDataRepository được xác định phạm vi trong UserComponent vì vậy cả MainActivity và SettingsActivity có thể chia sẻ cùng chúng đối tượng của nó.

Bởi vì chúng ta đang sử dụng chú thích phạm vi @ActivityScope để chú thích các thành phần mà Activity đang quản lý vòng đời của nó, chúng ta cần một phạm vi có thể bao gồm cả nhiều Activity nhưng nó không phải toàn bộ ứng dụng, chúng ta không có bất cứ phạm vi nào giống vậy, vì vậy chúng ta cần tạo mới một phạm vi khác.

Bởi vì phạm vi này bao gồm vòng đời từ khi người dùng đăng nhập, chúng ta có thể gọi nó là LoggedUserScope.

Tạo mới một tệp Kotlin, đặt tên là LoggedUserScope.kt trong gói user và định nghĩa một chú thích phạm vi LoggedUserScope như dưới đây:

app/src/main/java/com/example/android/dagger/user/LoggedUserScope.kt

@Scope @MustBeDocumented @Retention(value = AnnotationRetention.RUNTIME) annotation class LoggedUserScope

Chúng ta có thể chú thích cả UserComponent và UserDataRepository với chú thích này để UserComponent luôn luôn cung cấp cùng một đối tượng UserDataRepository.

UserComponent.kt

// Scope annotation that the UserComponent uses // Classes annotated with @LoggedUserScope will have a unique instance in this Component @LoggedUserScope @Subcomponent interface UserComponent { ... }

UserDataRepository.kt

// This object will have a unique instance in a Component that // is annotated with @LoggedUserScope (i.e. only UserComponent in this case). @LoggedUserScope class UserDataRepository @Inject constructor(private val userManager: UserManager) { ... }

Trong lớp MyApplication, chúng ta đã lưu trữ một đối tượng cần thiết userManager được thực hiện bằng cách thủ công. Bởi vì ứng dụng của chúng ta được tái cấu trúc hoàn toàn sử dụng Dagger, chúng ta không cần nó nữa. MyApplication bây giờ giống như dưới đây:

MyApplication.kt

open class MyApplication : Application() { val appComponent: AppComponent by lazy { DaggerAppComponent.factory().create(applicationContext) } }

Chúng ta cũng phải điều chỉnh AppComponent:

  1. Loại bỏ các phương thức inject bởi vì MainActivity và SettingActivity sẽ không được đưa vào bởi thành phần này nữa, chúng sẽ sử dụng UserComponent.
  2. Đưa vào một UserManager từ biểu đồ bởi vì MainActivity và SettingsActivity cần nó để truy cập đối tượng UserComponent.

AppComponent.kt

@Singleton @Component(modules = [StorageModule::class, AppSubcomponents::class]) interface AppComponent { ... // 2) Expose UserManager so that MainActivity and SettingsActivity // can access a particular instance of UserComponent fun userManager(): UserManager // 1) Remove following lines fun inject(activity: MainActivity) fun inject(activity: SettingsActivity) }

Trong SettingsActivity, chú thích ViewModel với @Inject ( bởi vì chúng ta muốn nó được đưa vào bởi Dagger) và loại bỏ từ khóa private. Để lấy một đối tượng UserComponent mà sẽ được khởi tạo khi người dùng đăng nhập, chúng ta gọi phương thức userManager() mà bây giờ appComponent đang hiện diện. Bây giờ, chúng ta có thể truy cập userComponent bên trong nó và đưa vào Activity.

SettingsActivity.kt

class SettingsActivity : AppCompatActivity() { // @Inject annotated fields will be provided by Dagger @Inject lateinit var settingsViewModel: SettingsViewModel override fun onCreate(savedInstanceState: Bundle?) { // Gets the userManager from the application graph to obtain the instance // of UserComponent and gets this Activity injected val userManager = (application as MyApplication).appComponent.userManager() userManager.userComponent!!.inject(this) super.onCreate(savedInstanceState) ... } ... }

MainActivity cũng làm tương tự để đưa vào UserComponent:

  1. UserManager không nên được đưa vào nữa bởi vì chúng ta có thể chuyển nó trực tiếp từ appComponent. Loại bỏ trường userManager.
  2. Tạo một biến cục bộ trước khi kiểm tra nó nếu người dùng đã đăng nhập hay chưa.
  3. Bởi vì UserComponent sẽ chỉ có sẵn khi người dùng đã đăng nhập, chúng ta lấy một đối tượng userComponent từ userManager và đưa vào Activity trong trường hợp else.

MainActivity.kt

class MainActivity : AppCompatActivity() { // 1) Remove userManager field @Inject lateinit var userManager: UserManager @Inject lateinit var mainViewModel: MainViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_settings) // 2) Grab userManager from appComponent to check if the user is logged in or not val userManager = (application as MyApplication).appComponent.userManager() if (!userManager.isUserLoggedIn()) { ... } else { setContentView(R.layout.activity_main) // 3) If the MainActivity needs to be displayed, we get the UserComponent // from the application graph and gets this Activity injected userManager.userComponent!!.inject(this) setupViews() } } ... }

Tất cả màn hình trong ứng dụng của chúng ta đã được tái cấu trúc với Dagger! Nếu bạn chạy ứng dụng, bạn có thể kiểm tra mọi thứ làm việc như mong đợi.

Sau khi thêm UserComponent, biểu đồ ứng dụng bây giờ giống như dưới đây:

Dagger Android là gì

Một trong các lợi ích của việc sử dụng framework dependency injection như Dagger là nó giúp cho việc kiểm thử mã của bạn dễ dàng hơn.

Unit tests

Bạn không phải sử dụng mã liên quan đến Dagger cho unit tests. Khi kiểm tra một lớp mà nó sử dụng việc đưa vào hàm khởi tạo, bạn không cần sử dụng Dagger để khởi tạo lớp đó. Bạn có thể gọi trực tiếp hàm khởi tạo của nó và truyền vào trực tiếp các phụ thuộc giả hoặc giả lập giống như cách bạn làm nếu chúng không được chú thích.

Ví dụ, nếu bạn xem xét tệp LoginViewModelTest.kt để kiểm tra LoginViewModel, chúng ta chỉ cần giả lập một đối tượng UserManager và truyền nó vào giống như chúng ta đã hoàn thành mà không cần Dagger.

LoginViewModelTest.kt

class LoginViewModelTest { ... private lateinit var viewModel: LoginViewModel private lateinit var userManager: UserManager @Before fun setup() { userManager = mock(UserManager::class.java) viewModel = LoginViewModel(userManager) } @Test fun `Get username`() { whenever(userManager.username).thenReturn("Username") val username = viewModel.getUsername() assertEquals("Username", username) } ... }

Tất cả unit tests giống như việc đưa các phụ thuộc vào bằng phương pháp thủ công, ngoại trừ một điều. Khi chúng ta thêm UserComponent.Factory vào UserManager, chúng ta đã phá vỡ unit tests. Chúng ta phải giả lập rằng điều gì Dagger sẽ trả về khi gọi phương thức create() trong Factory.

Mở tệp UserManagerTest.kt và tạo và cấu hình giả lập cho Factory UserComponent như dưới đây:

UserManagerTest.kt

class UserManagerTest { ... @Before fun setup() { // Return mock userComponent when calling the factory val userComponentFactory = Mockito.mock(UserComponent.Factory::class.java) val userComponent = Mockito.mock(UserComponent::class.java) `when`(userComponentFactory.create()).thenReturn(userComponent) storage = FakeStorage() userManager = UserManager(storage, userComponentFactory) } ... }

Bây giờ tất cả các unit test nên thành công.

Kiểm thử end-to-end

Chúng ta đã chạy các bài kiểm thử tích hợp mà không có Dagger. Ngay sau khi chúng ta giới thiệu Dagger trong dự án và thay đổi việc triển khai lớp MyApplication, chúng ta đã phá vỡ điều này.

Sử dụng Application tùy chỉnh trong các bài kiểm thử thiết bị

Trước đó, các bài kiểm thử đầu cuối của chúng ta sử dụng một ứng dụng tùy chỉnh, gọi là MyTestApplication. Để sử dụng một application khác, chúng ta phải tạo mới một TestRunner. Mã cho điều này là trong tệp app/src/androidTest/java/com/example/android/dagger/MyCustomTestRunner.kt. Mã đã có sẵn trong dự án, và bạn không phải thêm nó vào.

MyCustomTestRunner.kt

class MyCustomTestRunner : AndroidJUnitRunner() { override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application { return super.newApplication(cl, MyTestApplication::class.java.name, context) } }

Dự án biết rằng TestRunner này cần được sử dụng khi chạy các bài kiểm thử thiết bị vì nó được chỉ ra trong tệp app/build.gradle.

app/build.gradle

... android { ... defaultConfig { ... testInstrumentationRunner "com.example.android.dagger.MyCustomTestRunner" } ... } ...

Sử dụng Dagger trong các bài kiểm thử thiết bị

Chúng ta phải cấu hình MyTestApplication để sử dụng Dagger. Đối với việc kiểm thử tích hợp, phương pháp tốt nhất là tạo một TestApplicationComponent dành cho việc kiểm thử. Production và testing sử dụng các thành phần cấu hình khác nhau.

Sự khác biệt giữa cấu hình kiểm thử và cấu hình sản phẩm là gì? Thay vì sử dụng một SharedPreferencesStorage trong UserManager, chúng ta muốn sử dụng một FakeStorage. Điều gì tạo ra SharedPreferencesStorage? StorageModule.

Chúng ta phải chuyển StorageModule thành một dạng khác để sử dụng FakeStorage. Bởi vì điều này chỉ được yêu cầu cho việc kiểm thử thiết bị, chúng ta tạo một lớp mới này trong thư mục androidTest. Tạo mới một gói, gọi là di bên trong app/src/androidTest/java/com/example/android/dagger/.

Trong đó, tạo mới một tệp, đặt tên là TestStorageModule.kt.

app/src/androidTest/java/com/example/android/dagger/di/TestStorageModule.kt

// Overrides StorageModule in android tests @Module abstract class TestStorageModule { // Makes Dagger provide FakeStorage when a Storage type is requested @Binds abstract fun provideStorage(storage: FakeStorage): Storage }

Theo cách @Binds làm việc, thay vì khai báo một phương thức với SharedPreferencesStorage như một tham số, với TestStorageModule, chúng ta truyền vào FakeStorage làm tham số. Điều đó sẽ làm cho TestAppComponent sẽ tạo một đối tượng của lớp được triển khai từ Storage.

Dagger không biết cách tạo các đối tượng FakeStorage, vì vậy chúng ta luôn luôn chú thích nó vào hàm khởi tạo với @Inject.

FakeStorage.kt

class FakeStorage @Inject constructor(): Storage { ... }

Bây giờ chúng ta đã cung cấp một đối tượng FakeStorage khi Dagger yêu cầu một kiểu Storage.

Bởi vì môi trường production và testing sử dụng cấu hình thành phần khác nhau, chúng ta phải tạo một component khác đóng vai trò như AppComponent. Chúng ta sẽ gọi nó là TestAppComponent.

Hãy tạo một tệp Kotlin mới trong đường dẫn dưới đây:

app/src/androidTest/java/com/example/android/dagger/di/TestAppComponent.kt

@Singleton @Component(modules = [TestStorageModule::class, AppSubcomponents::class]) interface TestAppComponent : AppComponent

Chúng ta cần chỉ ra tất cả các Module trong component test này. Ngoài TestStorageModule, chúng ta cũng phải đưa vào các Module AppSubcomponents để thêm thông tin về subcomponents của nó. Bởi vì bạn không cần Context cho biểu đồ này (Chỉ phụ thuộc được yêu cầu Context trước đây là SharedPreferencesStorage), ở đó không cần định nghĩa một Factory cho TestAppComponent.

Nếu bạn thử biên dịch ứng dụng, MyTestApplication gây ra lỗi biên dịch như bạn phải bỏ đi đối tượng userManager từ lớp. Cũng như bạn sẽ thấy Dagger không sinh ra lớp thực hiện cho TestAppComponent. Nó nên được tạo ra một lớp DaggerTestAppComponent với biểu đồ test. Đó là vì kapt không đóng vai trò trong thư mục androidTest. Bạn phải thêm vào gói annotation processor của Dagger cho androidTest như dưới đây:

app/build.gradle

... dependencies { ... kaptAndroidTest "com.google.dagger:dagger-compiler:$dagger_version" }

Bây giờ nếu bạn đồng bộ dự án và biên dịch ứng dụng, DaggerTestAppComponent sẽ được sinh ra để sử dụng. Nếu không, là vì nó không đóng vai trò trong thư mục androidTest nữa, thử chạy bằng cách nhấn chuột phải vào thư mục java bên trong thư mục androidTest và nhấn Run All Tests'.

Chúng ta phải tạo vài thay đổi trong MyApplication để cho phép MyTestApplication tạo một thành phần Dagger riêng của nó.

Trích xuất việc khởi tạo appComponent trong by lazy vào một phương thức khác để chúng ta có thể override trong MyTestComponent, đặt tên là initializeComponents() và thêm từ khóa open.

MyApplication.kt

open class MyApplication : Application() { val appComponent: AppComponent by lazy { initializeComponent() } open fun initializeComponent(): AppComponent { return DaggerAppComponent.factory().create(applicationContext) } }

Bây giờ, chúng ta có thể tạo lớp con của MyApplication và sử dụng TestAppComponent trong MyTestApplication:

  1. Loại bỏ đối tượng userManager nếu bạn chưa hoàn thành trước đó.
  2. Override phương thức initializeComponent để trả về một đối tượng DaggerTestAppComponent.

MyTestApplication.kt

class MyTestApplication : MyApplication() { override fun initializeComponent(): AppComponent { // Creates a new TestAppComponent that injects fakes types return DaggerTestAppComponent.create() } }

Bây giờ việc kiểm thử nên thành công. Mở tệp ApplicationTest.kt trong thư mục androidTest/java/com/example/android/dagger và nhấn vào Button run

Dagger Android là gì
bên cạnh việc định nghĩa một lớp. Việc kiểm thử nên thực hiện và chạy thành công.

Có vài chú thích có thể hữu ích trong dự án Android.

@Provides

Cùng với các chú thích @Inject và @Binds, bạn có thể sử dụng chú thích @Provides để nói cho Dagger biết cách cung cấp một đối tượng của lớp bên trong Module Dagger.

Kiểu trả về của hàm với @Provides (không quan trọng nó được gọi như thế nào) nói cho Dagger biết kiểu gì được thêm vào biểu đồ. Các tham số của hàm đó là các phụ thuộc mà Dagger cần đáp ứng trước khi cung cấp một đối tượng của kiểu đó.

Trong ví dụ của chúng ta, chúng ta cũng có thể cung cấp việc triển khai cho kiểu Storage như dưới đây:

StorageModule.kt

@Module class StorageModule { // @Provides tell Dagger how to create instances of the type that this function // returns (i.e. Storage). // Function parameters are the dependencies of this type (i.e. Context). @Provides fun provideStorage(context: Context): Storage { // Whenever Dagger needs to provide an instance of type Storage, // this code (the one inside the @Provides method) will be run. return SharedPreferencesStorage(context) } }

Bạn có thể sử dụng chú thích @Provides trong Module Dagger để nói cho Dagger biết cách cung cấp:

  • Việc triển khai của giao diện (mặc dụ @Binds được khuyến khích bởi vì nó sinh ra ít mã và tóm lại nó hiệu quả hơn).
  • Các lớp mà dự án của bạn không sở hữu (tức là các đối tượng của Retrofit).

Qualifiers

Chúng ta không phải sử dụng Dagger qualifiers trong dự án chúng ta do tính đơn giản của nó. Qualifiers rất hữu ích khi bạn muốn thêm các cách triển khai khác nhau của cùng một kiểu giống nhau cho biểu đồ Dagger. Ví dụ, nếu chúng ta muốn các đối tượng Storage khác nhau được cung cấp, chúng ta có thể phân biệt chúng bằng việc sử dụng Qualifiers.

Ví dụ, nếu chúng ta có hàm SharedPreferencesStorage lấy tên của tệp như một tham số:

SharedPreferencesStorage.kt

class SharedPreferencesStorage @Inject constructor(name: String, context: Context) : Storage { private val sharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE) ... }

Chúng ta có thể thêm các cách triển khai khác nhau với @Provides trong StorageModule. Chúng ta có thể sử dụng Qualifiers để xác định loại của cách triển khai.

StorageModule.kt

@Retention(AnnotationRetention.BINARY) @Qualifier annotation class RegistrationStorage @Retention(AnnotationRetention.BINARY) @Qualifier annotation class LoginStorage @Module class StorageModule { @RegistrationStorage @Provides fun provideRegistrationStorage(context: Context): Storage { return SharedPreferencesStorage("registration", context) } @LoginStorage @Provides fun provideLoginStorage(context: Context): Storage { return SharedPreferencesStorage("login", context) } }

Trong ví dụ này, chúng ta định nghĩa hai Qualifiers: RegistrationStorage và LoginStorage mà chúng ta có thể sử dụng chú thích @Provides. Chúng ta đang thêm vào hai kiểu của Storage vào biểu đồ: RegistrationStorage và LoginStorage. Cả hai phương thức đều trả về Storage, có cùng các tham số (các phụ thuộc) nhưng tên khác nhau.

Bởi vì tên trong các hàm @Provides không có bất kỳ chức năng nào, chúng ta phải nhận chúng từ biểu đồ sử dụng Qualifiers như dưới đây:

Ví dụ về cách nhận Qualifiers như các phụ thuộc

// In a method class ClassDependingOnStorage(@RegistrationStorage private val storage: Storage) { ... } // As an injected field class ClassDependingOnStorage { @Inject @field:RegistrationStorage lateinit var storage: Storage }

Bạn có thể nhận được chức năng tương tự với Qualifiers với chú thích @Named, tuy nhiên Qualifiers được khuyến khích bởi vì:

  • Chúng có thể loại bỏ khỏi Proguard hoặc R8.
  • Chúng không cần giữ các hằng số dùng chung để trùng với các tên.
  • Chúng có thể được ghi lại.

Có một phần nữa của bài này cho bạn trải nghiệm - Thêm một màn hình SplashScreen. MainActivity.kt hiện tại quyết định màn hình nào được hiển thị dựa vào việc khi nào người dùng đã đăng ký hoặc đã đăng nhập. Đó là vấn đề bởi vì chúng ta đang thực hiện việc đưa vào các phụ thuộc có điều kiện, chỉ đưa vào khi người dùng đã đăng nhập và sẽ trong màn hình MainActivity.

Những bước này không chứa các nhận xét hoặc đoạn mã, vì vậy hãy tự mình thử:

  • Tạo một SplashActivity với một SplashViewModel quyết định màn hình nào được hiển thị.
  • Nhưng chúng ta đã làm, sử dụng dependency injection trong SplashActivity để nhận các trường được đưa vào bởi Dagger.
  • Loại bỏ các logic trong phương thức onCreate() trong MainActivity.kt bởi vì khi Activity được mở, thì người dùng đã đăng nhập rồi.

Bạn bây giờ đã làm quen với Dagger và bạn có thể thêm nó vào ứng dụng Android của bạn. Trong bài này bạn đã được học về:

  • Cách tạo một biểu đồ ứng dụng sử dụng chú thích Dagger @Component.
  • Cách thêm thông tin vào biểu đồ sử dụng các chú thích @Inject, @Module, @Binds, và @BindsInstance.
  • Cách tạo các container cho các luồng, sử dụng @Subcomponent.
  • Cách sử dụng lại các đối tượng trong các container khác nhau sử dụng @scope.
  • Dagger Qualifiers và chú thích @Provides.
  • Cách kiểm thử ứng dụng, sử dụng Dagger với unit test và các bài kiểm thử thiết bị.

Thế giới đang phát triển và thay đổi nhanh chóng, đặc biệt trong các lĩnh vực khoa học kỹ thuật, và công nghệ thông tin. Các công nghệ mới ra đời đã làm thay đổi lĩnh vực công nghệ thông tin, đặc biệt nền công nghiệp lập trình. Nắm bắt xu hướng công nghệ, CodelabsViet ra đời nhằm cung cấp các kiến thức lập trình được cập nhật mới nhất và được lựa chọn kỹ lưỡng và biên dịch từ các khóa học hiện đại và dễ hiểu của Google, trên tất cả các nền tảng mobile, web, material design, v.v..

Nội dung các khóa học CodeLabsViet

Hiện nay, các khóa học, giáo trình, tài liệu tiếng việt về lĩnh vực công nghệ thông tin khá ít, cách tiếp cận khó hiểu, thiếu nền tảng cơ bản cũng như thiếu các ví dụ hoặc tạo ra các chương trình cụ thể, mà người dùng có thể tương tác xuyên suốt khóa học. Với sứ mệnh thay đổi cách tiếp cận về các phương pháp hướng dẫn, các khóa học CodeLabsViet được xây dựng trên nền tảng công cụ của Google, CodeLabs. Đây là một công cụ mới, giúp người học có những trải nghiệm thú vị và thực hiện các chương trình nhỏ, cụ thể thông qua từng bài học. Trong mỗi bài học, chúng ta sẽ xác định cần chuẩn bị những gì cho mỗi bài học, sẽ học được gì và sẽ làm được gì cụ thể. Từng nhiệm vụ trong bài học sẽ có các tương tác và công việc cụ thể, các vấn đề nảy sinh, và hướng giải quyết các vấn đề đó như thế nào. Với cách tiếp cận như vậy, kiến thức của người học sẽ dần dần được tích lũy và trau dồi liên tục.

Đặc biệt, CodeLabsViet cung cấp các khóa học với kiến thức được cập nhật mới nhất và liên tục, với các công nghệ, ngôn ngữ lập trình và các mô hình lập trình mới nhất.

Tầm nhìn CodeLabsViet

Đội ngũ CodeLabsViet đã lựa chọn kỹ lưỡng từ các hàng trăm các khóa của Codelabs được cung cấp bởi Google, với các công nghệ và kỹ thuật mới nhất, để biên dịch, việt hóa hơn 1500 trang A4 nội dung, với các khóa học từ cơ bản đến nâng cao.

Kế hoạch CodelabsViet

  • Đến cuối năm 2020, biên dịch, việt hóa hơn 4000 trang A4 nội dung.
  • Đến hết năm 2021, biên dịch, việt hóa hơn 10000 trang A4 nội dung.
  • Bắt đầu từ 2022, CodeLabsViet sẽ tự biên soạn các khóa, và bổ sung các khóa học cho các lĩnh vực khác.

Đối tượng người dùng CodeLabsViet

  1. Các bậc phụ huynh có con mong muốn lập trình, có thể tham gia khóa học theo hướng dẫn cụ thể, thực hiện từng bước để tạo ra các chương trình có ý nghĩa. Thật tuyệt vời khi các bậc phụ huynh có con nhỏ, có thể tạo ra các ứng dụng có thể cài đặt lên chính thiết bị điện thoại di động của mình.
  2. Các sinh viên đang học các ngành lập trình, không biết tìm các kiến thức, chương trình tiếng việt ở đâu, có thể tham gia khóa học, để trau dồi các kiến thức cơ bản, các kỹ thuật mới được cập nhật mới nhất của xu hướng công nghệ.
  3. Các bạn sinh viên mới ra trường, cần có nền tảng kiến thức tốt của các lĩnh vực mà mình mong muốn xin việc. Nền tảng kiến thức tốt, là điều kiện cơ bản để các bạn có thể phỏng vấn tốt, và dễ dàng làm việc trong môi trường công nghệ thông tin, đặc biệt là lĩnh vực lập trình. Trong các bài của khóa học, luôn có các câu hỏi trắc nghiệm để ôn lại kiến thức bạn vừa học, giúp bạn chuẩn bị tốt hơn cho các đợt phỏng vấn xin việc.
  4. Các kỹ sư có kinh nghiệm, đã làm việc lâu năm trong lĩnh vực lập trình, tuy nhiên các kiến thức các bạn nắm bắt, đã không còn phù hợp với các kỹ thuật, cũng như công nghệ thay đổi liên tục. Tham gia CodeLabsViet để cập nhật các kiến thức mới, sử dụng các mô hình lập trình mới nhất, cũng như tái cấu trúc lại kiến thức mà mình đã sử dụng trước đây.
  5. Các kỹ sư lập trình có thể tham gia CodeLabsViet để đa dạng hóa kiến thức, bổ sung các kiến thức khác nhau để có cái nhìn tổng quan về công nghệ thế giới. Một kỹ sư lập trình Web, iOS mong muốn học lập trình về Android, hoặc các kỹ sư lập trình Mobile, Web có thêm kiến thức về thiết kế, về xu hướng màu, style của ứng dụng, để tạo ra các ứng dụng phù hợp với xu hướng của thế giới.
  6. Các chủ dự án, các nhà quản lý, các nhà tuyển dụng, phỏng vấn cũng cần nắm vững nền tảng kiến thức trong lĩnh vực lập trình tốt, để quản lý, ước lượng khối lượng công việc dự án, cũng như định hình được kiến trúc tổng quan dự án để làm việc với nhóm phát triển. Ngoài ra, các câu hỏi trắc nghiệm ở cuối mỗi bài học, có thể sử dụng để phỏng vấn các ứng viên tham gia phỏng vấn trong tất cả các mức độ vị trí phỏng vấn.

CodelabsViet - Chất lượng Việt - Nâng tầm kiến thức Việt

Donation

Để tạo thêm động lực cho CodeLabsViet tạo ra các khóa học chất lượng, nâng tầm kiến thức Việt, các bạn có thể ủng hộ cho đội ngũ CodeLabsViet ở các kênh dưới đây:

Tên tài khoản : BUI QUOC ANH Số tài khoản : 2481248

Dagger Android là gì