Dependency Injection Với Dagger 2
Khi các bạn học Design patterns , Các bạn sẽ chú ý đến 5 quy tắc chính trong Design pattern.Ở đây mình muốn nói tới quy tắc cuối cùng Dependency inversion principle . Các bạn hiểu như thế nào về quy tắc này ?? Theo ý hiểu của mình là như này: Các module cấp cao không nên phụ thuộc vào ...
Khi các bạn học Design patterns, Các bạn sẽ chú ý đến 5 quy tắc chính trong Design pattern.Ở đây mình muốn nói tới quy tắc cuối cùng Dependency inversion principle. Các bạn hiểu như thế nào về quy tắc này ??
Theo ý hiểu của mình là như này:
- Các module cấp cao không nên phụ thuộc vào các modules cấp thấp. Cả 2 nên phụ thuộc vào abstraction.
- Interface (abstraction) không nên phụ thuộc vào chi tiết, mà ngược lại. ( Các class giao tiếp với nhau thông qua interface, không phải thông qua implementation.)
Nguyên lý này khá lắt léo, mình sẽ lấy ví dụ thực tế. Chúng ta đều biết 2 loại đèn: đèn tròn và đèn huỳnh quang. Chúng cùng có đuôi tròn, do đó ta có thể thay thế đèn tròn bằng đèn huỳnh quanh cho nhau 1 cách dễ dàng.
Ở đây, interface chính là đuôi tròn, implementation là bóng đèn tròn và bóng đèn huỳnh quang. Ta có thể swap dễ dàng giữa 2 loại bóng vì ổ điện chỉ quan tâm tới interface (đuôi tròn), không quan tâm tới implementation.
Với cách code thông thường, các module cấp cao sẽ gọi các module cấp thấp. Module cấp cao sẽ phụ thuộc và module cấp thấp, điều đó tạo ra các dependency. Khi module cấp thấp thay đổi, module cấp cao phải thay đổi theo. Một thay đổi sẽ kéo theo hàng loạt thay đổi, giảm khả năng bảo trì của code.
Nếu tuân theo Dependendy Inversion principle, các module cùng phụ thuộc vào 1 interface không đổi. Ta có thể dễ dàng thay thế, sửa đổi module cấp thấp mà không ảnh hưởng gì tới module cấp cao.
Ưu điểm và khuyết điểm của Dependency Injection
Dĩ nhiên, Dependency Injection không phải vạn năng, nó cũng có những ưu điểm và khuyết điểm, do đó không phải project nào cũng nên áp dụng Dependency Injection. Với những dự án lớn, code nhiều, Dependency Injection là thứ rất cần thiền để đảm bảo code dễ bảo trì, dễ thay đổi. Vì vậy, bản thân các framework nổi tiếng như Spring, Struts2 … đều hỗ trợ hoặc tích hợp sẵn Dependency Injection.
ƯU ĐIỂM
- Giảm sự kết dính giữa các module.
- Code dễ bảo trì, dễ thay thế module.
- Rất dễ test và viết Unit Test.
- Dễ dàng thấy quan hệ giữa các module (Vì các dependecy đều được inject vào constructor).
KHUYẾT ĐIỂM
- Khái niệm Dependency Injection khá “khó tiêu”, các developer mới sẽ gặp khó khăn khi học.
- Sử dụng interface nên đôi khi sẽ khó debug, do không biết chính xác module nào được gọi.
- Các object được khởi tạo toàn bộ ngay từ đầu, có thể làm giảm performance.
- Làm tăng độ phức tạp của code.
Trong bài viết này mình sẽ giới thiệu tới các bạn một Framework trong Android được xây dựng trên ngữ cảnh Dependency Injection đó là Dagger 2
Dagger 2 API Dagger 2 đưa ra một số annotations đặc biệt:
- @Module Cho những class định nghĩa những method để cung cấp sự phụ thuộc (dependencies )
- @Provides Cho những method bên trong nhưng @Module class
- @Inject Yêu cầu 1 phụ thuộc (có thể là một constructor, một field hay một method)
- @Component Đây là interface dùng để nối giữa các module và các Inject
Để thự hiện Dagger 2 chính xác, bạn phải theo sau những bước:
Cấu Hình File Gradle
Tại root build.gradle
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.1' // Better IDE support for annotations (so Android Studio interacts better with Dagger) classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() } }
Chúng ta hãy chú ý một chút ở dependencies trong phần này chúng ta đã thêm một plugin, plugin này là rất hữu ích cho việc truy cập vào code đã được tạo ra bởi framework Dagger 2. Nếu bạn không thêm điều này, bạn sẽ nhìn thấy lỗi khi tham chiếu tới những class này.
Tại build.gradle của app.
apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' android { compileSdkVersion 24 buildToolsVersion "24.0.2" defaultConfig { applicationId "com.techdb.dagger2sample" minSdkVersion 19 targetSdkVersion 24 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" multiDexEnabled true } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:24.2.1' testCompile 'junit:junit:4.12' compile 'com.android.support:design:24.2.1' compile 'com.google.dagger:dagger:2.7' compile 'com.google.dagger:dagger-compiler:2.7' compile 'org.glassfish.main:javax.annotation:4.0-b33' compile 'com.google.code.gson:gson:2.7' compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2' compile 'com.squareup.okhttp:okhttp:2.4.0' compile 'com.android.support:multidex:1.0.1' }
Ở đây tôi đã thêm plugin apply plugin: 'com.neenbedankt.android-apt' sau android plugin dependencies:
- dagger : library của framework Dagger 2
- dagger-compiler: cho việc generate code
- javax.annotation : bổ sung thêm các annotation nằm ngoài phạm vi của Dagger
Thực Hiện Code Trong App
Trong phần này chúng ta sẽ thực hiện một vài đoạn code đơn giản :
Tạo một package model và định nghĩ một class:
public class Repository { String name; String fullName; String description; public Repository(String name, String fullName, String description) { this.name = name; this.fullName = fullName; this.description = description; } public String getName() { return name; } public String getFullName() { return fullName; } public String getDescription() { return description; } }
Tạo một package module Bạn tạo một class với annotation là @Module. Class này sẽ cung cấp những object cần thiết
@Module public class NetModule { String mBaseUrl; public NetModule(String baseUrl) { this.mBaseUrl = baseUrl; } @Provides @Singleton SharedPreferences provideSharedPreferences(Application application) { return PreferenceManager.getDefaultSharedPreferences(application); } @Provides @Singleton Cache provideOKHttpCache(Application application) { int cacheSize = 10 * 1024 * 1024;// 10Mi Cache cache = new Cache(application.getCacheDir(), cacheSize); return cache; } @Provides @Singleton Gson provideGson() { GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); return gsonBuilder.create(); } @Provides @Singleton OkHttpClient provideOkHttpClient(Cache cache) { OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.setCache(cache); return okHttpClient; } @Provides @Singleton Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) { Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create(gson)) .baseUrl(mBaseUrl) .client(okHttpClient) .build(); return retrofit; } }
Tạo Component:
@Singleton @Component(modules = {AppModule.class, NetModule.class}) public interface NetComponent { Retrofit retrofit(); OkHttpClient okHttpClient(); SharedPreferences sharedPreferences(); }
Trong lớp Application:
public class MyApplication extends Application { GitHubComponent mGitHubComponent; NetComponent mNetComponent; @Override public void onCreate() { super.onCreate(); mNetComponent = DaggerNetComponent.builder().appModule(new AppModule(this)) .netModule(new NetModule("https://api.github.com")).build(); mGitHubComponent = DaggerGitHubComponent.builder().netComponent(mNetComponent).gitHubModule(new GitHubModule()).build(); } public GitHubComponent getGitHubComponent() { return mGitHubComponent; } public NetComponent getNetComponent() { return mNetComponent; } }
public class MainActivity extends AppCompatActivity { @Inject SharedPreferences mSharedPreferences; @Inject Retrofit mRetrofit; @Inject GitHubApiInterface mGitHubApiInterface; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { Call<ArrayList<Repository>> call = mGitHubApiInterface.getRepository("lequanghoa"); call.enqueue(new Callback<ArrayList<Repository>>() { @Override public void onResponse(Response<ArrayList<Repository>> response, Retrofit retrofit) { if (response.isSuccess()) { Log.i("ERROR","Data => "+ response.body().toString()); Snackbar.make(v, "Data retrieved", Snackbar.LENGTH_LONG).setAction("Action", null).show(); } else { Log.i("ERROR", String.valueOf(response.code())); } } @Override public void onFailure(Throwable t) { } }); } }); ((MyApplication) getApplication()).getGitHubComponent().inject(this); } }
Để hiểu chi tiết hơn các bạn có thể tham khảo coded tại đây