iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 10
0
Software Development

Android Architecture系列 第 10

Dependency Injection with Dagger2: Part 2

  • 分享至 

  • xImage
  •  

昨天建好了Dagger2的環境,今天就把目前的程式用DI的方式來改寫,會比建置的過程輕鬆很多。

Inject to Fragment

RepoFragment中實例化了GithubViewModelFactory,我們將這邊改成用Inject的方式,首先將GithubViewModelFactory加入AppModule中:

@Module
class AppModule {

    @Provides
    @Singleton
    GithubService provideGithubService() {
        ...
    }

    @Provides
    @Singleton
    GithubViewModelFactory provideFactory() {
        return new GithubViewModelFactory();
    }
}

在BuildersModule中新增RepoFragment,方式與新增Activity相同:

@Module
public abstract class BuildersModule {

    @ContributesAndroidInjector
    abstract MainActivity contributeMainActivity();

    @ContributesAndroidInjector
    abstract RepoFragment contributeRepoFragment();
}

接著就是Inject了,而要在Fragment中使用@Inject的話需先在其Parent Activity加入HasSupportFragmentInjector

public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {

    @Inject
    DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        ...
    }

    @Override
    public AndroidInjector<Fragment> supportFragmentInjector() {
        return fragmentDispatchingAndroidInjector;
    }
}

之後就可以在Fragment中使用@Inject

public class RepoFragment extends Fragment {

    ...

    @Inject 
    GithubViewModelFactory factory;

    ...

    @Override
    public void onAttach(Context context) {
        AndroidSupportInjection.inject(this);
        super.onAttach(context);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        ...
    }
    
    ...
}

onAttach中呼叫AndroidSupportInjection.inject(this),就完成對Fragment Inject的操作了。

不過,這其中還有個不夠乾淨的地方,就是GithubViewModelFactory在內部實例化了DataModel:

public class GithubViewModelFactory implements ViewModelProvider.Factory {

    private DataModel dataModel;

    public GithubViewModelFactory() {
        this.dataModel = new DataModel();
    }

    ...
}

顯然這違反了IoC原則,我們應該用Inject的方式將DataModel加入GithubViewModelFactory才對。一種方式我們可以修改AppModule:

@Module
class AppModule {

    ...

    @Provides
    @Singleton
    DataModel provideDataModel() {
        return new DataModel();
    }

    @Provides
    @Singleton
    GithubViewModelFactory provideFactory(DataModel dataModel) {
        return new GithubViewModelFactory(dataModel);
    }
}

標註@Provides的method若有parameter的話,Dagger會找出其擁有的該型態物件來使用。我們在Module內新增了DataModel將其列入Dagger的管理下,接著在provideFactory()增加parameter變成provideFactory(DataModel dataModel),Dagger就會找出其管理的DataModel給provideFactory使用。

補充一下官方文件Q&A@Provides說明:

@Provides, the most common construct for configuring a binding, serves three functions:

  1. Declare which type (possibly qualified) is being provided — this is the return type
  2. Declare dependencies — these are the method parameters
  3. Provide an implementation for exactly how the instance is provided — this is the method body

除了上面的方式外,對於自訂的class還有另一種方式constructor injection可以使用,讓我們不用修改Module的內容,詳細如下說明。

Constructor Injection

當我們的自訂的class很多時,Module就會就會變得比較肥,所以對自訂的class我們可以用constructor injection的方式將其加入DI框架中並讓Module保持簡潔。

Constructor injection的用法是在class的constructor上標註@Inject將其加入Dagger的管理下,以剛剛的情境為例,我們要將GithubViewModelFactory和DataModel這兩個自訂的class加入Dagger,首先修改DataModel:

@Singleton
public class DataModel {

    ...

    @Inject
    public DataModel() {

    }

    ...
}

在DataModel建立constructor並標註@Inject,這樣的概念就如同將DataModel加入AppModule一樣,DataModel已經列入Dagger的管理之下,其他class可以用@Inject或是constructor parameter取得此DataModel。

修改GithubViewModelFactory:

@Singleton
public class GithubViewModelFactory implements ViewModelProvider.Factory {

    private DataModel dataModel;

    @Inject
    public GithubViewModelFactory(DataModel dataModel) {
        this.dataModel = dataModel;
    }

    ...
}

在constructor上也標註@Inject,將GithubViewModelFactory列入Dagger的管理中,就可以用parameter取得DataModel了。

用constructor injection或在Module中標註@Provides都一樣在Dagger的管理之下,所以可以互相取用,例如我們的DataModel可以用parameter取得在AppModule內的GithubService:

@Singleton
public class DataModel {

    private GithubService githubService;

    @Inject
    public DataModel(GithubService githubService) {
        this.githubService = githubService;
    }

    ...
}

以上就是constructor injection的用法,而怎麼決定要用constructor injection或是加入Module中用@Provides呢?一般來說我習慣將自訂的class用constructor injection來處理,只有像Retrofit那種外部library無法取得constructor的才加入Module中,以保持Module的簡潔。

Multiple Module

當Module比較肥的時候,可以將其內容分散到多個Module,例如建立NetworkModule管理網路相關的class:

@Module
class NetworkModule {

    @Provides
    @Singleton
    OkHttpClient provideOkHttpClient() {
        return new OkHttpClient().newBuilder()
                ...
    }

    @Provides
    @Singleton
    GithubService provideGithubService(OkHttpClient okHttpClient) {
        return new Retrofit.Builder()
                ...
    }
}

接著在Component中加入此Module就可以了:

@Singleton
@Component(modules = {
        AndroidSupportInjectionModule.class,
        ...
        NetworkModule.class})
public interface AppComponent {

    ...
}

若是Activity/Fragment比較多的話,也可以拆分BuildersModule,例如將Fragment的部分拆出來成為FragmentBuildersModule:

@Module
public abstract class FragmentBuildersModule {
    @ContributesAndroidInjector
    abstract RepoFragment contributeRepoFragment();
}

將原本的BuildersModule重新命名成ActivityBuildersModule:

@Module
public abstract class ActivityBuildersModule {
    @ContributesAndroidInjector(modules = FragmentBuildersModule.class)
    abstract MainActivity contributeMainActivity();
}

因為MainActivity中會建立RepoFragment所以要加上(modules = FragmentBuildersModule.class),若之後別的Activity沒有用到FragmentBuildersModule中的Fragment就可以不用這段。


這兩天利用Dagger建立了基本的DI框架,明天會依照GithubBrowserSample學習怎麼進一步使用Dagger讓程式更為簡潔,

GitHub source code:
https://github.com/IvanBean/ITBon2018/tree/day10-dagger-constructor-injection


上一篇
Dependency Injection with Dagger2: Part 1
下一篇
Dependency Injection with Dagger2: Part 3
系列文
Android Architecture30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言