2016年8月30日 星期二

How to use Dagger 2 in android

Lesson1
What is  Dependency Injection?


將依賴直接傳遞給類,而不是由類來初始化依賴
開發者的代碼低耦合,且能夠容易地進行測試
低耦合:令互相依賴降到最小,的對象間可以交互,但不清楚彼此的細節

How to install


apt 'com.google.dagger:dagger-compiler:2.0.2'
   compile 'com.google.dagger:dagger:2.0.2'
   provided 'javax.annotation:jsr250-api:1.0'



dependencies {
       classpath 'com.android.tools.build:gradle:1.0.0'
       classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
   }

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

Use
example by simple example:


I'm going to work with two classes, Vehicle and Motor. Motor is the independent class and Vehicle is the dependent class. I'm going to start creating this model within a new package called model.


public class Motor {
   private int rpm;
   public Motor(){
       this.rpm = 0;
   }
   public int getRpm(){
       return rpm;
   }
   public void accelerate(int value){
       rpm = rpm + value;
   }
   public void brake(){
       rpm = 0;
   }
}


public class Vehicle {
   private Motor motor;
   public Vehicle(Motor motor){
       this.motor = motor;
   }
   public void increaseSpeed(int value){
       motor.accelerate(value);
   }
   public void stop(){
       motor.brake();
   }
   public int getSpeed(){
       return motor.getRpm();
   }
}


we do not create a motor in the Vehicle, we just pass it into, because we want 降低耦合,Vehicle just need to know it has a motor,do not ned to know all the thing of the motor(if create a motor in the Vehicle,need know all about the motor)


now ,start use Dragger2!!!!

to create a class with the @Module annotation. This class is going to provide the objects you will need with its dependencies satisfied.


we need Motor and Vehicle,so

@Module
public class VehicleModule {
   @Provides @Singleton
   Motor provideMotor(){
       return new Motor();
   }
   @Provides @Singleton
   Vehicle provideVehicle(){
       return new Vehicle(new Motor());
   }
}

The @Singleton annotation indicates that there will be only one instance of the object.

 @Inject
public Vehicle(Motor motor){
   this.motor = motor;
}



Vehicle need a motor,and we already create a provider to provide the motor, so add @Inject to the method,   @Inject annotation to request dependencies in the constructor, fields, or methods


but! how the Module and InJect acn connect together???How can we get the Vehicle?
now,we want Inject the Motor into the Vehicle,so we need  VehicleModule.class(to provide the Motor,) inject into the Vehicle,need a provideVehicle in Component


@Component


we know the get Vehicle method in the VehicleModule
@Singleton
@Component(modules = {VehicleModule.class})
public interface VehicleComponent {
   Vehicle provideVehicle();
}

than we can use @Component Interface to Obtain Objects


When you try to create a new object of the interface with the @Component annotation, you have to do it using the prefix Dagger_<NameOfTheComponentInterface>, in this case Dagger_VehicleComponent, and then use the builder method to call every module within.

VehicleComponent component = Dagger_VehicleComponent.builder().vehicleModule(new VehicleModule()).build();
       vehicle = component.provideVehicle();

Try in Android
create a module that provide the context


@Module
public class ApplicationModule {
   private Application application;

   public ApplicationModule(Application application) {
       this.application = application;
   }

   @Provides
   @Singleton
   public Context provideContext() {
       return application;
   }

   @Provides
   @Singleton
   public SharedPreferences provideSharedPreferences(){
       return PreferenceManager.getDefaultSharedPreferences(application);
   }

}

------------------------------------------------------------------------------------------------------------------------
Lesson2
Inject to the target
aim:we want we can get the SharedPreferences  by dragger2 at Main Activity


fro mlesson 2  we already have this:


@Module
public class ApplicationModule {
   private Application application;

   public ApplicationModule(Application application) {
       this.application = application;
   }

   @Provides
   @Singleton
   public Context provideContext() {
       return application;
   }

   @Provides
   @Singleton
   public SharedPreferences provideSharedPreferences(){
       return PreferenceManager.getDefaultSharedPreferences(application);
   }

}


we can provide the SharedPreferences ,How can we give this to the MainActivity?

1.create the component interface


@Singleton
@Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
   void inject(MyApplication target);
   void inject(MainActivity target);
}


because we want to use in the MainActivity, so void inject(MainActivity target); must have

2.create ApplicationComponent


to create the ApplicationComponent ,we need ApplicationModule instance,the ApplicationModule  need a context,so we create the ApplicationComponent  in the Application


public class MyApplication extends Application {
   private ApplicationComponent component;

   public ApplicationComponent getComponent() {
       return component;
   }

   @Override
   public void onCreate() {
       super.onCreate();

       component = DaggerApplicationComponent.builder().applicationModule(new ApplicationModule(this)).build();



   }
}

3 Inject in to MainActivityt


public class MainActivity extends AppCompatActivity {
   @Inject SharedPreferences prefs;




((MyApplication)getApplication()).getComponent().inject(this);
-----------------------------------------------------------------------------------------------------------------------
Lesson3
How to perform constructor injection
How to perform method injection
How to provide named injections
How to require & perform named injections
How to create multiple Dagger modules
How and why it is important to rely on abstractions (interfaces)
How to provide a Retrofit RestAdpater
How to build a custom Retrofit Endpoint and inject it into a Restadapter
How a multi-module Dagger object graph is composed





API KEy:f2333815f5f8c240bcc9baf188f6540a

when we see the code,we can fins that in  CurrentConditionService


has


@Inject ForecastService forecastService;


and


@Override
   public void onCreate() {
       super.onCreate();
       ((TaskoApplication)getApplication()).getComponent().inject(this);
   }
it use Dragger 2 to inject here


ForecastService is a interface


so we need to add new Provider


into the ApplicationModule
@Module
public class ApplicationModule {

   private Application application;

   public ApplicationModule(Application application) {
       this.application = application;
   }

   @Provides @Singleton
   public Context provideContext() {
       return application;
   }

   @Provides @Singleton
   public SharedPreferences provideSharedPreferences(Context context) {
       return PreferenceManager.getDefaultSharedPreferences(context);
   }

   @Provides
   public ForecastService provideForecastService(RestAdapter restAdapter) {
       return new ForecastServiceImpl(restAdapter);
   }

}

The New Module

@Module
public class ApiModule {

   @Provides
   @Named(Constants.Injection.Named.FORECAST_API_KEY)
   public String provideForecastIOApiKey(Context context) {
       return context.getString(R.string.forecast_io_api_key);
   }

   @Provides
   public Endpoint provideEndpoint(@Named(Constants.Injection.Named.FORECAST_API_KEY) String apiKey) {
       return new ForecastIOApiEndpoint().setApiKey(apiKey);
   }

   @Provides
   @Singleton
   public RestAdapter provideRestAdapter(Endpoint endpoint) {
       return new RestAdapter.Builder()
               .setLogLevel(RestAdapter.LogLevel.FULL)
               .setEndpoint(endpoint)
               .build();
   }
}

we cen see that here is a @Named it ensure the

provideEndpoint ge the string form provideForecastIOApiKey


MultiModule


component = DaggerApplicationComponent.builder()
                           .applicationModule(new ApplicationModule(this))
                           .apiModule(new ApiModule())
                           .build();
inject to the new target

@Singleton
@Component(modules = { ApplicationModule.class, ApiModule.class })
public interface ApplicationComponent {
   void inject(TaskoApplication target);
   void inject(MainActivity target);
   void inject(CurrentConditionService target);
   void inject(MainFragment target);
}


The module get from the module


@Module
public class ApplicationModule {

   private Application application;

   public ApplicationModule(Application application) {
       this.application = application;
   }

   @Provides @Singleton
   public Context provideContext() {
       return application;
   }

   @Provides @Singleton
   public SharedPreferences provideSharedPreferences(Context context) {
       return PreferenceManager.getDefaultSharedPreferences(context);
   }

   @Provides
   public ForecastService provideForecastService(RestAdapter restAdapter) {
       return new ForecastServiceImpl(restAdapter);
   }

}



provideSharedPreferences  get from provideContext
@Module
public class ApiModule {

   @Provides
   @Named(Constants.Injection.Named.FORECAST_API_KEY)
   public String provideForecastIOApiKey(Context context) {
       return context.getString(R.string.forecast_io_api_key);
   }

   @Provides
   public Endpoint provideEndpoint(@Named(Constants.Injection.Named.FORECAST_API_KEY) String apiKey) {
       return new ForecastIOApiEndpoint().setApiKey(apiKey);
   }

   @Provides
   @Singleton
   public RestAdapter provideRestAdapter(Endpoint endpoint) {
       return new RestAdapter.Builder()
               .setLogLevel(RestAdapter.LogLevel.FULL)
               .setEndpoint(endpoint)
               .build();
   }
}
provideRestAdapter get from provideEndpoint get from provideForecastIOApiKey get from provideContext

all is auto do by Dagger2

-----------------------------------------------------------------------------------------------------------------------
Lesson 5
Testing with Dagger 2 and Mockito


Espresso,need this
How can we test it with Dagger 2 and Mockito

Aim:check will the view show the wanted data?

use the Mockito to Mock the ForecastService,than use Espresso to check it

Below show how to achieve this:

first ,we need to create the Modeuse model ,it retur the Mocked ForecastService

@Module
public class MockDemoModule {
   @Provides @Singleton
   public ForecastService providesForecastService() {
       return Mockito.mock(ForecastService.class);
   }
}


second,because has a new Module ,we need to create a new Component

@Singleton
@Component(modules = { MockDemoModule.class })
public interface TestingComponent extends DemoComponent {
   void inject(MainActivityTest target);
}
MainActivityTest is the testing class

third,in the lesson 2 and 3 app,we create the instance of the component in the Application,in the test,we create the  component in the Application,but we need to create a specificed Application for test,and hold the test component.

public class MockDemoApplication extends DemoApplication {

   @Override
   public DemoComponent createComponent() {
       return DaggerTestingComponent.builder()
               .mockDemoModule(new MockDemoModule()).build();
   }
}

how to make the Android use this component when testing?
set it in AndroidJUnitRunner

public class MockDemoTestRunner extends AndroidJUnitRunner {
   @Override
   public Application newApplication(ClassLoader cl, String className, Context context)
           throws InstantiationException, IllegalAccessException, ClassNotFoundException {
       return super.newApplication(cl, MockDemoApplication.class.getName(), context);
   }
}

at the build.gradle,set to use MockDemoTestRunner'

defaultConfig {
       applicationId "io.caster.daggertesting"
       minSdkVersion 16
       targetSdkVersion 23
       versionCode 1
       versionName "1.0"

       testInstrumentationRunner 'io.caster.daggertesting.MockDemoTestRunner'
   }

…..later ,learn Espresso first

otheruseful thing


----------------------------------------------------------------------------------------------------------------------------
Dagger 2 Scopes 1 and 2
@Singleton
means there will be one instance of the object for the lifetime of the component, not for the lifetime of the JVM.Fo the hole Application

How do define our scope?

if no scope,injector create an instance,use for one,than forget it,if have scope,injector will reuse the instancein later injection.
Let we create s a scope only available for the profile fragment.

first,define the Scope name

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ProfileScope {
}

Second,use it

@ProfileScope
@Subcomponent(modules = { ProfileModule.class })
public interface ProfileComponent {
   void inject(ProfileFragment target);
}

@Module
public class ProfileModule {

   // Profile Screen based dependencies go here

   @Provides
   public SomeBigObject providesSomeBigObject() {
       return new SomeBigObject();
   }
}

that mean the it will reuse only in the Profile.
subcompoent mean :
inherits the bindings from a parent,
at here,the parent component is Appcomponent

so

@Singleton
@Component(modules = {AppModule.class, AndroidModule.class})
public interface AppComponent {
   void inject(DemoApplication target);

   ProfileComponent plus(ProfileModule ProfileModule);

   SettingsComponent plus(SettingsModule settingsModule);
}

how can we get the ProfileComponent to use?
at Application se tt he get method first:

public ProfileComponent createProfileComponent() {
       profileComponent = appComponent.plus(new ProfileModule());
       return profileComponent;
   }
and we need tomange the life cycle by ourself,in application,create this method

public void releaseProfileComponent() {
       profileComponent = null;
   }

than call at the ProfileFragment,because we want it only scope at the ProfileFragment

@Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       ((DemoApplication) getActivity().getApplication()).createProfileComponent().inject(this);

   }

when destory,release

@Override
   public void onDestroy() {
       super.onDestroy();
       ((DemoApplication) getActivity().getApplication()).releaseProfileComponent();
   }

The different component:
when require the sub component ,the parent component will together,sho can use two or more component at the same time,they can have different scope.