Nội dung
1. Giới thiệu khái niệm về sự phụ thuộc
2. Dependency injection with Dagger 2
3. Exercise: Dependency injection with Dagger 2
4. Dagger 2 and Android
5. Exercise: Dependency injection in Android activities with Dagger 2
6. Replacing @Module classes in tests
7. About this website
8. Dagger resources
1. Giới thiệu về sự phụ thuộc
- Trong java, 1 lớp được coi là phụ thuộc vào lớp khác nếu nó sử dụng 1 thể hiện của lớp này.
- Ví dụ: Ta có 2 lớp, lớp Motor và Vehicle, do lớp Vehicle có gọi lớp Motor nên Vehicle phụ thuộc vào lớp Motor
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(); } }
2. Sự phụ thuộc với Dagger 2
-
Dagger 2 là gì?
– Dagger 2 là 1 framework về sự phụ thuộc. Tạo ra những đoạn code rất dễ đọc và dễ debug.
-
Các annotations của Dagger 2
- @Module và @Providers: Định nghĩa cho các lớp và các hàm nào là lớp phụ thuộc.
- @Inject: Yêu cầu 1 sự phụ thuộc (Hàm khởi tạo, 1 trường hoặc 1 phương thức)
- @Component: Là nối interface giữa các module và injection
- Lưu ý: Không sử dụng injection cho các phương thức, trường private.
3. Ví dụ
- Thêm vào build.gradle tại đường dẫn https://github.com/google/dagger
- Bật File -> Other Setting -> Default Settings -> Build, Execution, Deployment -> Annotation Processor -> Bật Enable Annotation Processing
- Blog hướng dẫn dagger: Blog
- Mã nguồn mở: LiveData, RxJava, Dagger
- Mã nguồn của bài viết: https://github.com/livelaptrinh/Dagger2
4. Bắt đầu
Đầu tiên, chúng ta định nghĩa cấu trúc của 1 ứng dụng android. Bao gồm các lớp nền bên dưới
- DataManager: Cung cấp các truy cập dữ liệu đến kho dữ liệu cục bộ trên trong app.
- DbHelper: Là lớp quản lý SQLite và sẽ được gọi trong lớp DataManager.
- SharedPrefsHelper: Là lớp quản lý SharedPreferences và cũng được gọi trong lớp DataManager.
- Các Model class để lấy DB.
Bước 1:
- Tạo ứng dụng mới và thêm thư viện
dependencies { compile "com.google.dagger:dagger:2.8" annotationProcessor "com.google.dagger:dagger-compiler:2.8" provided 'javax.annotation:jsr250-api:1.0' compile 'javax.inject:javax.inject:1' }
Bước 2:
- Tạo lớp User để tạo bảng trong SQLite
package laptrinh.live.dagger2.data.model; public class User { private Long id; private String name; private String address; private String createdAt; private String updatedAt; public User() { } public User(String name, String address) { this.name = name; this.address = address; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getCreatedAt() { return createdAt; } public void setCreatedAt(String createdAt) { this.createdAt = createdAt; } public String getUpdatedAt() { return updatedAt; } public void setUpdatedAt(String updatedAt) { this.updatedAt = updatedAt; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", address='" + address + '\'' + ", createdAt='" + createdAt + '\'' + ", updatedAt='" + updatedAt + '\'' + '}'; } }
Bước 3:
- Tạo các annotations tùy chỉnh
@Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface ActivityContext { }
@Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface ApplicationContext { }
@Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface DatabaseInfo { }
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface PerActivity { }
Notes:
- @Qualifier: Dùng để xác định đầy đủ, đôi khi mình sử dụng Application Context và Activity Context cả 2 điều sử dụng Context.
- @Scope: được sử dụng để xác định phạm vi trong đó một đối tượng phụ thuộc vẫn tồn tại.
Bước 4:
Tạo lớp DbHelper kế thừa từ lớp SQLiteOpenHelper. Đây là lớp quản lý tất cả các bảng có liên quan tới SQLite.
package laptrinh.live.dagger2.data; import android.content.ContentValues; import android.content.Context; import android.content.res.Resources; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import javax.inject.Inject; import javax.inject.Singleton; import laptrinh.live.dagger2.data.model.User; import laptrinh.live.dagger2.di.ApplicationContext; import laptrinh.live.dagger2.di.DatabaseInfo; /** * Created by MSI on 2/5/2018. */ @Singleton public class DbHelper extends SQLiteOpenHelper { //USER TABLE public static final String USER_TABLE_NAME = "users"; public static final String USER_COLUMN_USER_ID = "id"; public static final String USER_COLUMN_USER_NAME = "usr_name"; public static final String USER_COLUMN_USER_ADDRESS = "usr_add"; public static final String USER_COLUMN_USER_CREATED_AT = "created_at"; public static final String USER_COLUMN_USER_UPDATED_AT = "updated_at"; @Inject public DbHelper(@ApplicationContext Context context, @DatabaseInfo String dbName,@DatabaseInfo int version) { super(context, dbName, null, version); } @Override public void onCreate(SQLiteDatabase db) { tableCreateStatements(db); } @Override public void onUpgrade(SQLiteDatabase db, int i, int i1) { db.execSQL("DROP TABLE IF EXISTS " + USER_TABLE_NAME); onCreate(db); } private void tableCreateStatements(SQLiteDatabase db) { try { db.execSQL( "CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(" + USER_COLUMN_USER_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + USER_COLUMN_USER_NAME + " VARCHAR(20), " + USER_COLUMN_USER_ADDRESS + " VARCHAR(50), " + USER_COLUMN_USER_CREATED_AT + " VARCHAR(10) DEFAULT " + getCurrentTimeStamp() + ", " + USER_COLUMN_USER_UPDATED_AT + " VARCHAR(10) DEFAULT " + getCurrentTimeStamp() + ")" ); } catch (SQLException e) { e.printStackTrace(); } } protected User getUser(Long userId) throws Resources.NotFoundException, NullPointerException { Cursor cursor = null; try { SQLiteDatabase db = this.getReadableDatabase(); cursor = db.rawQuery( "SELECT * FROM " + USER_TABLE_NAME + " WHERE " + USER_COLUMN_USER_ID + " = ? ", new String[]{userId + ""}); if (cursor.getCount() > 0) { cursor.moveToFirst(); User user = new User(); user.setId(cursor.getLong(cursor.getColumnIndex(USER_COLUMN_USER_ID))); user.setName(cursor.getString(cursor.getColumnIndex(USER_COLUMN_USER_NAME))); user.setAddress(cursor.getString(cursor.getColumnIndex(USER_COLUMN_USER_ADDRESS))); user.setCreatedAt(cursor.getString(cursor.getColumnIndex(USER_COLUMN_USER_CREATED_AT))); user.setUpdatedAt(cursor.getString(cursor.getColumnIndex(USER_COLUMN_USER_UPDATED_AT))); return user; } else { throw new Resources.NotFoundException("User with id " + userId + " does not exists"); } } catch (NullPointerException e) { e.printStackTrace(); throw e; } finally { if (cursor != null) cursor.close(); } } protected Long insertUser(User user) throws Exception { try { SQLiteDatabase db = this.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put(USER_COLUMN_USER_NAME, user.getName()); contentValues.put(USER_COLUMN_USER_ADDRESS, user.getAddress()); return db.insert(USER_TABLE_NAME, null, contentValues); } catch (Exception e) { e.printStackTrace(); throw e; } } private String getCurrentTimeStamp() { return String.valueOf(System.currentTimeMillis() / 1000); } }
Notes:
- @Singleton: Đảm bảo chỉ có 1 thể hiện trên toàn bộ các lớp.
- @Inject: Trên hàm khởi tạo để chỉ cho Dagger tích lũy các tham số phụ thuộc khi lớp được xây dựng.
- @ApplicationContext Qualifier giúp cho DbHelper lấy đối tượng context trong ứng dụng.
- @DatabaseInfo giúp cho Dagger phân biệt giữa String và Integer.
Bước 5:
package laptrinh.live.dagger2.data; import android.content.SharedPreferences; import javax.inject.Inject; import javax.inject.Singleton; /** * Created by MSI on 2/5/2018. */ @Singleton public class SharedPrefsHelper { public static String PREF_KEY_ACCESS_TOKEN = "access-token"; private SharedPreferences mSharedPreferences; @Inject public SharedPrefsHelper(SharedPreferences sharedPreferences) { mSharedPreferences = sharedPreferences; } public void put(String key, String value) { mSharedPreferences.edit().putString(key, value).apply(); } public void put(String key, int value) { mSharedPreferences.edit().putInt(key, value).apply(); } public void put(String key, float value) { mSharedPreferences.edit().putFloat(key, value).apply(); } public void put(String key, boolean value) { mSharedPreferences.edit().putBoolean(key, value).apply(); } public String get(String key, String defaultValue) { return mSharedPreferences.getString(key, defaultValue); } public Integer get(String key, int defaultValue) { return mSharedPreferences.getInt(key, defaultValue); } public Float get(String key, float defaultValue) { return mSharedPreferences.getFloat(key, defaultValue); } public Boolean get(String key, boolean defaultValue) { return mSharedPreferences.getBoolean(key, defaultValue); } public void deleteSavedData(String key) { mSharedPreferences.edit().remove(key).apply(); } }
Notes: Lớp này cũng là lớp Singleton
Bước 6:
Tạo lớp DataManager
package laptrinh.live.dagger2.data; import android.content.res.Resources; import javax.inject.Inject; import javax.inject.Singleton; import javax.naming.Context; import laptrinh.live.dagger2.data.model.User; import laptrinh.live.dagger2.di.ApplicationContext; /** * Created by MSI on 2/5/2018. */ @Singleton public class DataManager { private Context mContext; private DbHelper mDbHelper; private SharedPrefsHelper mSharedPrefsHelper; @Inject public DataManager(@ApplicationContext Context mContext, DbHelper mDbHelper, SharedPrefsHelper mSharedPrefsHelper) { this.mContext = mContext; this.mDbHelper = mDbHelper; this.mSharedPrefsHelper = mSharedPrefsHelper; } public void saveAccessToken(String accessToken) { mSharedPrefsHelper.put(SharedPrefsHelper.PREF_KEY_ACCESS_TOKEN, accessToken); } public String getAccessToken(){ return mSharedPrefsHelper.get(SharedPrefsHelper.PREF_KEY_ACCESS_TOKEN, null); } public Long createUser(User user) throws Exception { return mDbHelper.insertUser(user); } public User getUser(Long userId) throws Resources.NotFoundException, NullPointerException { return mDbHelper.getUser(userId); } }
Notes: Lớp này thể hiện sự phụ thuộc tới Context, DbHelper, SharedPrefsHelper trong hàm khởi tạo. Nó cung cấp tất cả các api để truy cập kho dữ liệu của ứng dụng.
Bước 7:
Tạo lớp DemoApplication
package laptrinh.live.dagger2; import android.app.Application; import android.content.Context; import javax.inject.Inject; import laptrinh.live.dagger2.data.DataManager; /** * Created by MSI on 2/5/2018. */ public class DemoApplication extends Application { @Inject DataManager dataManager; public static DemoApplication get(Context context){ return (DemoApplication)context.getApplicationContext(); } }
Notes: Thêm vào Manifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="laptrinh.live.dagger2"> <application android:name=".DemoApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Bước 8:
Chúng ta phải cung cấp những phụ thuộc trong lớp DemoApplication. Chúng ta cung cấp DataManager và để cung cấp lớp này thì chúng ta phải cung cấp các phụ thuộc của DataManager là: Context, DbHelper và SharedPrefsHelper. Sau đó chúng ta cần cung cấp thêm các yêu cầu của các lớp đó.
- Context phải là ApplicationContext
- DbHelper cần Context, dbName, và Version. Và không có thêm nữa.
- SharedPrefsHelper cần SharedPreferences
Bây giờ chúng ta cung cấp các phụ thuộc này cho DemoApplication
package laptrinh.live.dagger2.di.module; import android.app.Application; import android.content.Context; import android.content.SharedPreferences; import dagger.Module; import dagger.Provides; import laptrinh.live.dagger2.di.ApplicationContext; import laptrinh.live.dagger2.di.DatabaseInfo; /** * Created by MSI on 2/5/2018. */ @Module public class ApplicationModule { private Application mApplication; public ApplicationModule(Application application) { this.mApplication = application; } @Provides @ApplicationContext Context provideContext(){ return mApplication; } @Provides Application provideApplication () { return mApplication;} @Provides @DatabaseInfo String provideDatabaseName() {return "demo-dagger.db";} @Provides @DatabaseInfo Integer provideDatabaseVersion() {return 2;} @Provides SharedPreferences provideSharedPrefs(){ return mApplication.getSharedPreferences("demo-prefs", Context.MODE_PRIVATE); } }
Bước 9:
Chúng ta tạo ra 1 Component để liên kết 2 class DemoApplication và ApplicationModule
package laptrinh.live.dagger2.di.component; import android.app.Application; import android.content.SharedPreferences; import javax.inject.Singleton; import javax.naming.Context; import dagger.Component; import laptrinh.live.dagger2.DemoApplication; import laptrinh.live.dagger2.data.DataManager; import laptrinh.live.dagger2.data.DbHelper; import laptrinh.live.dagger2.di.ApplicationContext; import laptrinh.live.dagger2.di.module.ApplicationModule; /** * Created by MSI on 2/5/2018. */ @Singleton @Component(modules = ApplicationModule.class) public interface ApplicationComponent { void inject(DemoApplication demoApplication); @ApplicationContext Context getContext(); Application getApplication(); DataManager getDataManager(); SharedPreferences getSharedPreferencesHelper(); DbHelper getDeHelper(); }
Notes: ApplicationComponent là 1 interface được implement từ Dagger2. Sử dụng @Component để chỉ rõ interface này là 1 conponent
Bước 11:
Tương tự, Chúng ta tạo module cho MainActivity và 1 Component.
package laptrinh.live.dagger2.di.module; import android.app.Activity; import android.content.Context; import dagger.Module; import dagger.Provides; import laptrinh.live.dagger2.di.ActivityContext; /** * Created by MSI on 2/5/2018. */ @Module public class ActivityModule { private Activity mActivity; public ActivityModule(Activity mActivity) { this.mActivity = mActivity; } @Provides @ActivityContext Context provideContext(){ return mActivity; } @Provides Activity provideActivity(){ return mActivity; } }
package laptrinh.live.dagger2.di.component; import android.app.Activity; import javax.inject.Singleton; import dagger.Component; import dagger.Module; import laptrinh.live.dagger2.MainActivity; import laptrinh.live.dagger2.di.PerActivity; import laptrinh.live.dagger2.di.module.ActivityModule; /** * Created by MSI on 2/5/2018. */ @PerActivity @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }
Cập nhật lại lớp DemoApplication
package laptrinh.live.dagger2; import android.app.Application; import android.content.Context; import javax.inject.Inject; import dagger.internal.DaggerCollections; import laptrinh.live.dagger2.data.DataManager; import laptrinh.live.dagger2.di.component.ApplicationComponent; import laptrinh.live.dagger2.di.component.DaggerApplicationComponent; import laptrinh.live.dagger2.di.module.ApplicationModule; /** * Created by MSI on 2/5/2018. */ public class DemoApplication extends Application { protected ApplicationComponent applicationComponent; @Inject DataManager dataManager; public static DemoApplication get(Context context){ return (DemoApplication)context.getApplicationContext(); } @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.builder() .applicationModule(new ApplicationModule(this)) .build(); applicationComponent.inject(this); } public ApplicationComponent getComponent(){ return applicationComponent; } }
Bước 12:
Tạo lớp MainActivity
package laptrinh.live.dagger2; import android.os.PersistableBundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; import javax.inject.Inject; import laptrinh.live.dagger2.data.DataManager; import laptrinh.live.dagger2.data.model.User; import laptrinh.live.dagger2.di.component.ActivityComponent; import laptrinh.live.dagger2.di.component.DaggerActivityConponent; import laptrinh.live.dagger2.di.module.ActivityModule; public class MainActivity extends AppCompatActivity { @Inject DataManager mDataManager; private TextView mTvUserInfo; private TextView mTvAccessToken; private ActivityComponent activityComponent; public ActivityComponent getActivityComponent(){ if (activityComponent == null){ activityComponent = DaggerActivityConponent.builder() .activityModule(new ActivityModule(this)) .applicationComponent(DemoApplication.get(this).getComponent()) .build(); } return activityComponent; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getActivityComponent().inject(this); mTvUserInfo = (TextView) findViewById(R.id.tv_user_info); mTvAccessToken = (TextView) findViewById(R.id.tv_access_token); } @Override public void onPostCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) { super.onPostCreate(savedInstanceState, persistentState); createUser(); getUser(); mDataManager.saveAccessToken("ASDR12443JFDJF43543J543H3K543"); String token = mDataManager.getAccessToken(); if(token != null){ mTvAccessToken.setText(token); } } private void createUser(){ try { mDataManager.createUser(new User("Ali", "1367, Gurgaon, Haryana, India")); }catch (Exception e){e.printStackTrace();} } private void getUser(){ try { User user = mDataManager.getUser(1L); mTvUserInfo.setText(user.toString()); }catch (Exception e){e.printStackTrace();} } }
Kết quả: