Dagger 2 trong Android - giảm số lượng method

Dagger 2 - là một dependency injection framework thuộc dạng compile-time và fully static. Tách biệt khởi tạo/sử dụng, dễ dàng test hơn(test đơn vị và chức năng), khả năng mở rộng tốt hơn - đây chỉ là một vài lợi ích từ việc sử dụng depedency injection framework như Dagger 2.

Sau một vài bài viết trước trình bày làm thế nào để làm việc với DI. Bài viết này sẽ chia sẻ kinh nghiệm làm việc với Dagger 2 trong phát triển ứng dụng production.

@Component với @Subcomponent

Không được trình bày rõ ràng trong các tutorial đơn giản, nhưng tuỳ chỉnh phạm vi (custom scope) là một trong những tính năng mạnh mẽ nhất trong Dagger 2. Trong các application phức tạp, chỉ sử dụng phạm vi @Singleton là không đủ để giải quyết vấn đề.

Hãy xem xét ví dụ đơn giản: chúng ta có các phụ thuộc (depedencies) được kết nối chặt chẽ với người dùng (user) hiện đang đăng nhập (ví dụ: class UserProfilePresenter chịu trách nhiệm về logic trong màn hình UserProfile). Thay vì gọi tới bộ lưu trữ dữ liệu mỗi khi cần thực thể user, chúng ta có thể tạo ra phạm vi @User và giữ tất cả các phụ thuộc liên quan như là một single instance trong UserComponent mà tồn tại (live) trong suốt quá trình user đang còn đăng nhập.

Trong Dagger 2, chúng ta có 2 cách để thực hiện custom scope là kế thừa hoặc mở rộng đồ thị phụ thuộc (object graph):

  • Chúng ta có thể xây dựng một @Component khác để chỉ rõ Component nào được mở rộng (trong trường hợp này là AppComponent):
@UserScope
@Component(
    modules = UserModule.class,
    dependencies = AppComponent.class
)
public interface UserComponent {
    UserProfileActivity inject(UserProfileActivity activity);
}
  • Hoặc, chúng ta có thể định nghĩa một @Subcomponent được tạo ra từ Component cơ sở với abstract factory method:
@UserScope
@Subcomponent(
    modules = UserModule.class
)
public interface UserComponent {
    UserProfileActivity inject(UserProfileActivity activity);
}

//===== AppComponent.java =====

@Singleton
@Component(modules = {...})
public interface AppComponent {
    // Factory method to create subcomponent
    UserComponent plus(UserModule module);
}

Sự khác biệt giữa 2 phương pháp này là gì?

@Subcomponent có quyền truy cập vào tất cả các phụ thuộc (depedencies) từ Component cha mẹ. Trong khi @Component chỉ được truy cập vào những depedencies được công khai (public) trong interface của Component cơ sở.

Khi số lượng method trở thành vấn đề

Tuy nhiên, có một sự khác biệt quan trọng nữa giữa @Subcomponent@Component. Chúng ta hãy cùng nhìn vào phần generated code được Dagger 2 tự động tạo ra. Phần implement của interface Subcomponent chỉ là một inner class bên trong Component.

Cụ thể, UserProfileActivityComponent mà phụ thuộc vào AppComponent sẽ tạo ra code như thế này:

public final class DaggerAppComponent implements AppComponent {

    //...AppComponent code...

    private final class UserProfileActivityComponentImpl implements UserProfileActivityComponent {
        private final UserProfileActivityComponent.UserProfileActivityModule userProfileActivityModule;
        private Provider<UserProfileActivity> provideActivityProvider;
        private Provider<UserProfileActivityPresenter> userProfileActivityPresenterProvider;
        private MembersInjector<UserProfileActivity> userProfileActivityMembersInjector;

        private UserProfileActivityComponentImpl(
            UserProfileActivityComponent.UserProfileActivityModule userProfileActivityModule) {
            this.userProfileActivityModule = Preconditions.checkNotNull(userProfileActivityModule);
            initialize();
        }

        private void initialize() {
            this.provideActivityProvider = DoubleCheck.provider(BaseActivityModule_ProvideActivityFactory.create(userProfileActivityModule));

            this.userProfileActivityPresenterProvider = DoubleCheck.provider(
                UserProfileActivityPresenter_Factory.create(
                    MembersInjectors.<UserProfileActivityPresenter>noOp(),
                    provideActivityProvider,
                    DaggerAppComponent.this.logoutManagerProvider,
                    DaggerAppComponent.this.userManagerProvider)
            );

            this.userProfileActivityMembersInjector = UserProfileActivity_MembersInjector.create(
                DaggerAppComponent.this.logoutManagerProvider,
                DaggerAppComponent.this.userManagerProvider
                userProfileActivityPresenterProvider)
            );
        }

        @Override
        public UserProfileActivity inject(UserProfileActivity activity) {
            userProfileActivityMembersInjector.injectMembers(activity);
            return activity;
        }
    }
}

Dòng 20-31 chỉ cho chúng ta thấy cách Dagger cung cấp các depedencies cần thiết từ AppComponent tới UserProfileActivityComponent. Subcomponent có quyền truy cập vào các fields được cung cấp của Component cơ sở để chúng có thể được sử dụng trực tiếp.

Mọi thứ là khác nhau với phụ thuộc @Component. Cấu trúc tương tự (UserProfileActivityComponent phụ thuộc vào AppComponent ) sẽ được tạo ra code như sau:

public final class DaggerUserProfileActivityComponent implements UserProfileActivityComponent {
    private Provider<LogoutManager> logoutManagerProvider;
    private Provider<UserManager> userManagerProvider;

    //...

    private DaggerUserProfileActivityComponent(Builder builder) {
        assert builder != null;
        initialize(builder);
    }

    @SuppressWarnings("unchecked")
    private void initialize(final Builder builder) {
        this.logoutManagerProvider = new Factory<LogoutManager>() {
            private final AppComponent appComponent = builder.appComponent;

            @Override
            public LogoutManager get() {
                return Preconditions.checkNotNull(
                    appComponent.getLogoutManager(),
                    "Cannot return null from a [email protected] component method");
            }
        };

        this.userManagerProvider = new Factory<UserManager>() {
            private final AppComponent appComponent = builder.appComponent;

            @Override
            public UserManager get() {
                return Preconditions.checkNotNull(
                    appComponent.getUserManager(),
                    "Cannot return null from a [email protected] component method");
            }
        };

        //... more providers ....
        
    }

    @Override
    public UserProfileActivity inject(UserProfileActivity activity) {
        userProfileActivityMembersInjector.injectMembers(activity);
        return activity;
    }

    //...
}

Dòng 14-34 cho thấy rằng, mỗi dependency yêu cầu từ AppComponent cần phải có method riêng mà cung cấp đối tượng Provider<> cho nó. Tại sao? Bởi vì Component phụ thuộc chỉ có thể truy cập vào những dependencies trong Component cơ sở được exposed với public interface của nó.

Điều này có ý nghĩa gì với chúng ta - những người phát triển ứng dụng Android? Mỗi dependency trong componet phụ thuộc được lấy từ component cơ sở sẽ khiến số lượng method ngày càng gần số lượng giới hạn là 65k mothod.

Phân tích APK một cách nhanh chóng

Bắt đầu từ Android Studio 2.2, có một tính năng mới cho phép chúng ta phân tích file APK. Chúng ta có thể truy cập từ Build -> Analyze APK….

Kết luận

Bởi vì vậy, chúng ta nên quyết định sử dụng @Subcomponent thay vì @Component phụ thuộc. Làm như vậy chúng ta có thể giảm được rất nhiều số lượng method (trong trường hợp của tôi là ~5000 methods).