Recent Posts

Those who are free of resentful thoughts surely find peace. - Buddha

Dagger2 - Part2

Posted on 29th Oct 2017


<-Back to Blogs

First, we have to define the structure of the Android App. The core classes are as follows:

  1. DataManager class will provide access to the data of the application.
  2. DbHelper class will be used by DataManager to access the SQLite database.
  3. SharedPrefsHelper will be used by DataManager to access the SharedPreferences.
  4. Model classes for retrieving the DB table.

Step 1:

Create a project in android studio with an empty Activity and add the following dependencies in app’s build.gradle

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'
}

Notes: We are using the annotation processor provided by gradle for android. dagger-compiler is the annotation processing repo for generating the dependency graph classes during build time. Other gradle dependencies are provided for the sake of Dagger2.

Step 2:

The data part we will be building first. So, create a model class User.

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 + '\'' +
  '}';
  }
  } 

Notes: This class will bind the DB table data.

Step 3:

Create few custom annotations: ActivityContextApplicationContext , DatabaseInfo , PerActivity

@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:

Why are we creating these annotations and what is @Qualifier and @Scope ?

@Qualifier annotation is provided by javax inject package and is used to qualify the dependency. For example, a class can ask both, an Application Context and an Activity Context. But both these Objects will be of type Context. So, for Dagger2 to figure out which variable is to be provided with what, we have to explicitly specify the identifier for it.

Thus @Qualifier is used to distinguish between objects of the same type but with different instances. In the above code, we have ActivityContext and ApplicationContext so that the Context object being injected can refer to the respectiveContext type. DatabaseInfo is used to provide the database namein the class dependency. Since a String class in being provided as a dependency, it always a good idea to qualify it so that the Dagger can explicitly resolve it.

An alternative to this is @Named annotation provided by Dagger2.@Named itself is annotated with @Qualifier. With @Named we have to provide string identifier for similar class objects and this identifier is used to map the dependency of a class. We will explore the @Named at the end of this example.

@Scope is used to specify the scope in which a dependency object persists. If a class getting dependencies, have members injected with classes annotated with a scope, then each instance of that class asking for dependencies will get its own set of member variables.

Step 4:

Create a DbHelper class that extends the SQLiteOpenHelper. This class will be responsible for all the DB related operations.

@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 Integer version) {
super(context, dbName, null, version);
}

@Override
public void onCreate(SQLiteDatabase db) {
tableCreateStatements(db);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
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: This class introduces few annotation.

  1. @Singleton ensure a single instance of a class globally. So, there will be only one DbHelper class instance for the app and whenever a class asks for DbHelper as a dependency, it will be provided with the same instance that is maintained in the Dagger’s dependency graph.
  2. @Inject on the constructor instructs the Dagger to accumulate all the parameter dependencies when the class is being constructed.
  3. @ApplicationContext Qualifier facilitates DbHelper to get the context object of the application from dagger’s dependency graph
  4. @DatabaseInfo qualifier helps the dagger to distinguish between String and Integer Dependencies from existing same types in the dependency graph.

We will again come back to this discussion when we deal with the module.

Rest of the contents of this class is standard SQLiteOpenHelper . This class creates a user table and inserts/reads it.

Step 5:

Create a SharedPrefsHelper to deal with the shared preferences.

@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();
  }
  }

This class also gets SharedPreferences dependency through Dagger which is expressed by the @Inject on the constructor.Notes: This class is annotated with @Singleton to make this a singleton class in the dependency graph of Dagger.

How is this dependency provided? It is explained later in this example.

Step 6:

Create DataManager class

@Singleton
  public class DataManager {
   
  private Context mContext;
  private DbHelper mDbHelper;
  private SharedPrefsHelper mSharedPrefsHelper;
   
  @Inject
  public DataManager(@ApplicationContext Context context,
  DbHelper dbHelper,
  SharedPrefsHelper sharedPrefsHelper) {
  mContext = context;
  mDbHelper = dbHelper;
  mSharedPrefsHelper = sharedPrefsHelper;
  }
   
  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: This class expresses the dependencies of Application ContextDbHelper and SharedPrefsHelper in the contructor. It provides all the apis to access the data of the application.


Next part here - part3 we will create demo application in android. We would learn how to create dependencies & we would be using in our app. Happy learning! Please do leave your comments below if you like the articles.


<-Back to Blogs

Categories

Good, better, best. Never let it rest. Untill your good is better and your better is best. - St. Jerome

© SOFTHINKERS 2013-18 All Rights Reserved. Privacy policy