Dagger's Learning Notes

Keywords: Retrofit github Android

Dependency Injection

public class ClassA {
    ClassB b;

    public ClassA() {
        b = new ClassB();
    }
}

Creating an object within a class with the new keyword creates a dependency. ClassA relies on ClassB, because the construction method of ClassB has been modified and parameters are needed, so not only the code of ClassB should be changed, but also the code of ClassA should be changed, so that the coupling degree is high and the expansibility is poor.

Dependency injection is not to actively initialize dependencies, but to pass in dependencies to host classes through external inputs, that is, not to create objects with new, such as:

  • Injection by construction method

    public class ClassA {
        ClassB b;
    
        public ClassA(ClassB b) {
            this.b = b;
        }
    }
  • set method injection

    public class ClassA {
        ClassB b;
    
        public void setClassB(ClassB b) {
            this.b = b;
        }
    }

Dagger2

public class MainActivity extends AppCompatActivity implements MainContract.View {
    private MainPresenter mainPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Activity depends on Presenter
        mainPresenter = new MainPresenter(this);

        mainPresenter.loadData();
    }
}

public class MainPresenter {
    private MainContract.View mView;

    MainPresenter(MainContract.View view) {
        mView = view;
    }

    public void loadData() {
        // Call the model layer method to load data
        ...
        //When the callback method succeeds
        mView.updateUI();
    }
}

In ordinary MVP instances, Presenter objects are constructed in Activity, resulting in the coupling of the two. With Dagger2, dependencies are injected into the host class through annotations:

public class MainActivity extends AppCompatActivity implements MainContract.View {

    @Inject
    MainPresenter mainPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DaggerMainComponent.builder()
            .mainModule(new MainModule(this))
            .build()
            .inject(this);

        mainPresenter.loadData();
    }
}

public class MainPresenter {
    private MainContract.View mView;

    @Inject
    MainPresenter(MainContract.View view) {
        mView = view;
    }    
    public void loadData() {
        //Call the model layer method to load data
        ...
        //When the callback method succeeds
        mView.updateUI();
    }
}

@Module
public class MainModule {
    private final MainContract.View mView;

    public MainModule(MainContract.View view) {
        mView = view;
    }

    @Provides
    MainView provideMainView() {
        return mView;
    }
}

@Component(modules = MainModule.class)
public interface MainComponent {
    void inject(MainActivity activity);
}
  • Inject @Inject where you want to create objects
    MainActivity adds a comment @Inject, indicating that MainPresenter needs to be injected into MainActivity, that is, MainActivity depends on MainPresenter. It is important to note that when @Inject is used, the member attributes of the class cannot be modified with the private modifier.

  • The constructor of the class to be injected uses @Inject
    The @Inject annotation is also added to the constructor of MainPresenter. In this way, mainPresenter in MainActivity establishes some connection with his constructor. This connection can be understood as follows: when a class is marked with @Inject, it goes to its constructor, and if the constructor is also marked with @Inject, the class is automatically initialized to complete dependency injection.

  • To inject a class without a constructor, create a class annotated with @Module
    For example, third-party class libraries, system classes, and the View interface of the above example can not be constructed by @Inject, so create a class named MainModlue annotated with @Module annotation.
    The MainContract.View membership property is declared in the MainModule class, the view passed in from outside is assigned to the mView in the constructor, and the view is returned through a @Provides annotated method that starts with a provider, which starts with a provider to provide dependencies.
    We can create multiple ways to provide different dependencies. If the @Provides annotated method requires parameters, it is the return value of another @Provides annotated method. Such as:

    @Module
    public class AppModule {
        @Provides
        @Singleton // Represents that after creation, acquisition will not create a new one
        public OkHttpClient provideOkHttpClient() {
            OkHttpClient okhttpClient = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .build();
            return okhttpClient;
        }
    
        @Provides
        public Retrofit provideRetrofit(OkHttpClient okhttpClient) {
            Retrofit retrofit = new Retrofit.Builder()
                .client(okhttpClient)
                .baseUrl("https://api.github.com")
                .build();
            return retrofit;
        }
    }

    A Module is a normal class, and it can also have constructive methods, as well as other features of normal classes.

  • Associate the host class with the class to be injected through @Component
    Component is an interface or abstract class annotated with the @Component annotation. Define an inject() method whose parameter type is the host class MainActivity, indicating where to inject; without a constructor, write modules = MainModule.class on the annotation, and a Component class can contain multiple Module classes to provide dependencies (such as @Component (modules = AppModule. class, GithubApiModule. cl) Ass}).

  • Complete dependencies in host classes
    A rebuild project generates a Compponent class prefixed with Dagger, which is DaggerMain Component, and then completes the following code in MainActivity:

    DaggerMainComponent.builder()
        .mainModule(new MainModule(this))
        .build()
        .inject(this);

    The view is passed to the MainModule through the new MainModule(this), and then the provideMainView() method in the MainModule returns to the View. When the MainPresenter is de-instantiated, it is found that the constructor has a parameter. At this point, the method that provides the dependency is looked up in the Module and passed in the View. Completed the view injection in presenter.

Main notes:

  • @ Inject: This annotation is usually used where you need to rely. In other words, use it to tell Dagger that the class or field needs dependency injection. In this way, Dagger constructs an instance of this class and satisfies their dependencies.
  • @ Module: The methods in the Modules class specifically provide dependencies, so we define a class annotated with @Module so that Dagger knows where to find the required dependencies when constructing an instance of the class. An important feature of modules is that they are designed to be partitioned and grouped together (for example, in our app, there are multiple modules that can be grouped together).
  • @ Provides: In modules, the method we define is to use this annotation to tell Dagger that we want to construct objects and provide these dependencies.
  • @ Component: Components are essentially an injector, or a bridge between @Inject and @Module. Their main function is to connect the two parts. Components can provide instances of all defined types.
  • @ Scope: Scopes are very useful. Dagger2 can limit the scope of annotations by customizing annotations.

Reference resources:
Dagger 2: From introduction to abandonment to sudden enlightenment
Preliminary use of Dagger 2
Android Unit Testing (6): Dependency Injection Using dagger2 and Its Application in Unit Testing

Posted by alanzhao on Sun, 30 Jun 2019 15:37:06 -0700