[Android] Dagger 2 - Phần IV: A new horizon
android
87
dependency injection
18
dagger
6
White

Hoa Anh Tú viết ngày 23/04/2021

Bài viết là phần cuối của series bài học vỡ lòng về Dagger 2. Nếu bạn chưa đọc phần trước, bạn có thể ghi danh vào lớp học tại đây

Các bài học để lên lớp

  1. [Android] Dagger 2 - Phần I: Basic principles
  2. [Android] Dagger 2 - Phần II: Into the Dagger 2
  3. [Android] Dagger 2 - Phần III - 1: The time of our dependencies
  4. [Android] Dagger 2 - Phần III - 2: The time of our dependencies
  5. [Android] Dagger 2 - Phần IV: A new horizon

Trong bài học trước...

Chúng ta đã thành công trong việc chia tách dependency graph ban đầu thành các dependency graph nhỏ hơn nhưng vẫn không làm mất đi tính kết nối giữa chúng. Ngoài ra, lời giải của bài toán chia để trị cũng giúp cho việc quản lý vòng đời của các dependency trở nên đơn giản hơn vài phần.

Đi vào bài học hôm nay...

Chúng ta sẽ điểm qua nốt những phần nhỏ nhỏ còn lại mà sẽ giúp cho việc implement Dagger vốn đã bớt phức tạp sau khi bạn hiểu cách Dagger hoạt động, nay còn đơn giản hơn. Ngoài ra, một số phần mình thấy là sẽ hữu dụng sẽ được trình bày ở bài viết này, bạn có thể lướt qua và tự nhủ rằng: "Wow, hóa ra Dagger có cả chức năng này ah!" rồi một lúc nào đó, bạn gặp một usecase tương ứng thì bạn vẫn biết là Dagger vẫn có thể giải quyết nó dễ dàng.

Photo by Jonatan Pie on Unsplash

Khởi tạo component

Nếu như đã lướt qua cả 4 phần trước, hẳn sẽ có lúc bạn thấy hơi rối một chút về việc khởi tạo component khi lúc thì chúng ta sử dụng Builder, lúc lại "bắt buộc" phải dùng Factory. Vậy thì rốt cuộc, sự khác nhau giữa hai cách khởi tạo này là gì? Phần này của bài viết sẽ giải đáp nỗi niềm đắn đo ấy.

Câu trả lời cho câu hỏi trên là chúng ta sẽ có 2 style để khởi tạo một component. Đó là sử dụng Factory hoặc Builder. Và mình gọi là style bởi vì mình nghĩ việc chọn cái nào đơn giản là thói quen hoặc sở thích của mỗi người và vì thế nên không có cách nào là bắt buộc cả ;)

Factory

Với Factory, chúng ta cần tạo một interface (thường chúng ta sẽ đặt luôn tên là Factory) và annotate nó với annotation @Component.Factory (hoặc @Subcomponent.Factory nếu component đó là subcomponent). Bên trong interface đó, chúng ta cần khai báo một method có kiểu trả về là chính component đó. Việc khởi tạo component sau này sẽ thông qua interface và method đó.

@Singleton
@Component(modules = [ApplicationSubComponent::class, UtilsModule::class, ApiModule::class])
interface ApplicationComponent {

    @Component.Factory
    interface Factory {
        fun create() : ApplicationComponent
    }
}

Và đoạn code khởi tạo component sẽ như thế này:

val applicationComponent = DaggerApplicationComponent
            .factory()
            .create()

Tuy nhiên, khi chạy chương trình, chúng ta thấy Dagger báo lỗi sau:

ApplicationComponent.java:21: error: @Component.Factory method is missing parameters for required modules or components: [io.srinnix.playground.dagger2.automatic.module.UtilsModule]

Issue này xảy ra vì UtilsModule cần một dependency mà chỉ có thể được khởi tạo bên ngoài component, đó là Context. Bởi vậy, UtilsModule cũng cần được truyền vào từ bên ngoài. Tức là chúng ta cần thêm tham số cho method create() để truyền UtilsModule vào cho component.

@Component.Factory
interface Factory {
    fun create(utilsModule: UtilsModule) : ApplicationComponent
}

Việc khởi tạo component sẽ thành như thế này:

val applicationComponent = DaggerApplicationComponent
            .factory(UtilsModule(applicationContext)
            .create()
BindInstance

Có một cách khác để truyền các dependency được khởi tạo bên ngoài component thay vì truyền cả module vào. Đó là sử dụng annotation @BindInstance. Thay vì truyền cả UtilsModule vào, chúng ta chỉ cần truyền chính dependency được khởi tạo bên ngoài vào và thêm @BindInstance vào trước tham số của method create():

@Component.Factory
interface Factory {
    fun create(@BindsInstance @ApplicationContext context: Context): ApplicationComponent
}

Khi sử dụng @BindInstance, Dagger sẽ biết cần thêm instance mà bạn truyền vào vào dependency graph để khi có chỗ nào request, instance này sẽ được provide. Với UtilsModule, chúng ta không cần truyền Context thông qua constructor nữa:

@Module
object UtilsModule { ... }

Và khi khởi tạo, chúng ta sẽ truyền Context vào là xong:

val applicationComponent = DaggerApplicationComponent
            .factory()
            .create(applicationContext)
Builder

Với Builder, Dagger đã mặc định gen cho các component một class Builder để khởi tạo component (nếu chúng ta không khai báo interface @Component.Factory). Mình đã sửa lại UserComponent thành phụ thuộc vào ApplicationComponent và đây là class Builder của DaggerUserComponent được gen ra:

public static final class Builder {
    private RepositoryUserModule repositoryUserModule;

    private ApplicationComponent applicationComponent;

    private Builder() {
    }

    @Deprecated
    public Builder userModule(UserModule userModule) {
      Preconditions.checkNotNull(userModule);
      return this;
    }

    public Builder repositoryUserModule(RepositoryUserModule repositoryUserModule) {
      this.repositoryUserModule = Preconditions.checkNotNull(repositoryUserModule);
      return this;
    }

    public Builder applicationComponent(ApplicationComponent applicationComponent) {
      this.applicationComponent = Preconditions.checkNotNull(applicationComponent);
      return this;
    }

    public UserComponent build() {
      if (repositoryUserModule == null) {
        this.repositoryUserModule = new RepositoryUserModule();
      }
      Preconditions.checkBuilderRequirement(applicationComponent, ApplicationComponent.class);
      return new DaggerUserComponent(repositoryUserModule, applicationComponent);
    }
  }

Chúng ta thấy rằng: với mỗi module mà component khai báo, Dagger cũng sẽ gen tương ứng một method để truyền module đó từ bên ngoài vào. Tuy nhiên, thực chất chúng ta chỉ phải phải truyền module mà chúng ta thực sự sử dụng hoặc module có chứa dependency được truyền vào từ bên ngoài (UtilsModule trong trường hợp ApplicationComponent). Với các module còn lại, một là với những module có sử dụng, Dagger sẽ tự khởi tạo cho chúng ta (RepositoryUserModule); hai là với những unused module, Dagger sẽ tự động bỏ qua.

Việc khởi tạo component sẽ như sau:

val userComponent = DaggerUserComponent.builder()
            .applicationComponent((context as? MyApplication)?.applicationComponent)
            .build()

Dagger annotation cheat sheet

Tuy là chưa đề cập đến Hilt- một phiên bản rút gọn của Dagger. Mình xin phép re-up một cái ảnh liệt kê những mô tả lướt qua về các annotation trong Dagger.

Nguồn: Google

Cùng nhìn lại

Ơn giàng, ơn Đảng, cuối cùng cũng đã đến phần này rồi ^^ Mình nghĩ chúng ta có thể tạm đặt một dấu chấm cho series này ở đây được rồi. Tuy vẫn còn một số phần mình chưa đề cập đến trong series này như Lazy initialization, Multi bindings, Testing hay Sử dụng Dagger với multi-module app... nhưng mình nghĩ: sẽ cần thời gian để bạn tiêu hóa được những phần cơ bản nhất đã, một khi bạn đã hiểu Dagger sẽ làm gì và làm như thế nào, những phần còn lại sẽ không còn quá khó khăn để nắm bắt nữa. Và mình cũng muốn dành đất cho sự "đói khát kiến thức" của bạn nữa ;)

Good luck and good night.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Hoa Anh Tú

8 bài viết.
15 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
17 8
Khi xây dựng một màn hình có dạng một list các item trong Android, ta sẽ phải thiết kế layout cho các item của list đó, list đó có thể như thế này ...
Hoa Anh Tú viết hơn 3 năm trước
17 8
White
6 1
Upload ảnh là một chức năng thường thấy ở các ứng dụng trên smartphone hiện nay. Tuy nhiên, khi upload một tấm ảnh lên, các ứng dụng thường sẽ scal...
Hoa Anh Tú viết gần 3 năm trước
6 1
White
5 1
Tại Google I/O 2014, cùng với sự ra mắt của Android Lollipop, Google đã giới thiệu RecyclerView _a better ListView_ với nhiều cải tiến cho phép gia...
Hoa Anh Tú viết hơn 3 năm trước
5 1
Bài viết liên quan
Male avatar
0 1
Introduction Dependency injection (DI) is a technique widely used in programming and well suited to Android development, where dependencies are pr...
Hades viết 11 tháng trước
0 1
White
0 0
Bài viết là phần thứ II của series bài học vỡ lòng về Dagger 2. Nếu bạn chưa đọc phần trước, bạn có thể ghi danh vào lớp học (Link) Các bài học đ...
Hoa Anh Tú viết 3 tháng trước
0 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
8 bài viết.
15 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!