📜 ⬆️ ⬇️

RoboGuice or "Android hooked on injections"

image RoboGuice is a library that allows you to enjoy all the benefits of Contexts and Dependency Injection when developing applications on Android.
As you can guess, RoboGuice is based on Google Guice .
At once I will make a reservation that as a translation of the word “injection” I will use the word “injection”.

Why prick?


I think that many readers will immediately have a question: “Why are these difficulties with CDI on a mobile platform? Surely this all takes up a lot of space and works slowly. ”
I will try to convince such readers of the opposite, namely that CDI on a mobile platform is very viable and makes life much easier for developers.

RoboGuice
Lyrical digression

After the epoch of fascination and the widespread use of XML, there was a sobering up when everyone was tired of the "wrinkles" of XML files. Began the search for new approaches that would reduce the amount of configuration code and speed up development. One of these outputs was CDI, based on annotations. The second can be called the approach " programming under agreements " ( Convention over configuration or coding by convention ). Developers liked it, ideas developed, several CDI implementations appeared, many large libraries began to use CDI through annotations.
As a result, CDI was standardized for Java in the form of JSR-330 , as well as ( JSR-299 ), officially included in Java EE 6.
Personally, I first saw the CDI in the Seam library. And I was pleasantly surprised by the simplicity and elegance of the approach. Saving development time, simplifying configuration, reducing the total volume and, accordingly, possible errors.
Over time, annotations have penetrated almost everywhere. After the XML mania, the era of annotations has arrived. And it would be very strange if they did not get to Android.

So why is it on a mobile platform?

The answer is simple: to make development faster and easier, and the code is cleaner and more reliable.
Not to be unfounded, let's take an example from Roboguice Get Started .
This is what an ordinary Activity class looks like:
class AndroidWay extends Activity { TextView name; ImageView thumbnail; LocationManager loc; Drawable icon; String myName; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); name = (TextView) findViewById(R.id.name); thumbnail = (ImageView) findViewById(R.id.thumbnail); loc = (LocationManager) getSystemService(Activity.LOCATION_SERVICE); icon = getResources().getDrawable(R.drawable.icon); myName = getString(R.string.app_name); name.setText( "Hello, " + myName ); } } 

Here are 19 lines of code. 5 lines of code in the onCreate() method is a trivial initialization of class fields, and only one, the last line carries the meaning: name.setText (). More complex implementations of the Activity classes can contain even more code for initializing objects.
Now compare with what RoboGuice allows:
 class RoboWay extends RoboActivity { @InjectView(R.id.name) TextView name; @InjectView(R.id.thumbnail) ImageView thumbnail; @InjectResource(R.drawable.icon) Drawable icon; @InjectResource(R.string.app_name) String myName; @Inject LocationManager loc; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); name.setText( "Hello, " + myName ); } } 

Now onCreate() much more concise and easier to onCreate() ; minimum template code, maximum code relating to the application logic.
')

First shot


To make the “first shot” more vital, I will give a good familiar example from developer.android.com on injections: Notepad Tutorial . It is perfectly disassembled on the shelves, its source codes are available and it will be quite enough to modify and implement the ideas of CDI.
To start using RoboGuice in a project, you need to add two libraries to the classpath: RoboGuice and guice-2.0-no_aop.jar (but not guice-3.0).
You can also add as a dependency in maven (yes, the Andoird project and Maven are quite compatible, but this is a topic for a separate article):
 <dependency> <groupId>org.roboguice</groupId> <artifactId>roboguice</artifactId> <version>1.1</version> </dependency> 

Then you need to create a class that inherits from RoboApplication :
 public class NotepadApplication extends RoboApplication { } 

and register it in AndroidManifest.xml :
 <application android:name="com.android.demo.notepad3.NotepadApplication"> 


Now we will rewrite a little NoteEdit class. Add resource injections and NotesDbAdapter , inherit from RoboActivity .
It was:
 public class NoteEdit extends RoboActivity { private NotesDbAdapter mDbHelper; private EditText mTitleText; private EditText mBodyText; private Long mRowId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.note_edit); setTitle(R.string.edit_note); mDbHelper = new NotesDbAdapter(this); mDbHelper.open(); mTitleText = (EditText) findViewById(R.id.title); mBodyText = (EditText) findViewById(R.id.body); Button confirmButton = (Button) findViewById(R.id.confirm); ... } ... } 

It became:
 public class NoteEdit extends RoboActivity { @Inject NotesDbAdapter mDbHelper; @InjectView(R.id.title) EditText mTitleText; @InjectView(R.id.title) EditText mBodyText; @InjectView(R.id.confirm) Button confirmButton; @InjectResource(R.string.edit_note) String title; private Long mRowId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.note_edit); setTitle(title); mDbHelper.open(); ... } ... } 

Approximately also modify Notepadv3 . Inherit from RoboListActivity , add an injection for the NotesDbAdapter and remove the initialization of this NotesDbAdapter from onCreate() .
 public class Notepadv3 extends RoboListActivity { ... @Inject private NotesDbAdapter mDbHelper; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.notes_list); mDbHelper.open(); fillData(); registerForContextMenu(getListView()); } ... } 

And we update the NotesDbAdapter constructor NotesDbAdapter that the Context automatically substituted with RoboGuice.
  @Inject public NotesDbAdapter(Context ctx) { this.mCtx = ctx; } 

We start, we check - it works!
The code became less, functionality remained, readability increased.
It is worth noting that RoboGuice can make injections not only of the Context class, but also of many other service objects of Android, including Application , Activity and various services. A full list is available here .

We increase the dose


To further demonstrate the capabilities of RoboGuice, we will refactor the NotesDbAdapter class. In my opinion, it is heavily overloaded and requires the decomposition of the DatabaseHelper and the NotesDbAdapter itself. As the project grows, DatabaseHelper will definitely be used by several classes. For example, a TasksDbAdapter appears. Moreover, I personally do not like the violent challenge mDbHelper.open(); . This method should be called in two places. It is tempting to forget to make this call and get an error. So let's get started.
Let's start with NotesDbAdapter . Here are a bunch of extra fields:

In fact, I would like to leave only SQLiteDatabase mDb . The rest is removed. In addition, we will make NotesDbAdapter “singleton”.
Total we have the following goal:
 @Singleton public class NotesDbAdapter { public static final String KEY_TITLE = "title"; public static final String KEY_BODY = "body"; public static final String KEY_ROWID = "_id"; private static final String TAG = "NotesDbAdapter"; private static final String DATABASE_TABLE = "notes"; @Inject private SQLiteDatabase mDb; public NotesDbAdapter() { } ... } 

KEY_TITLE, KEY_BODY, KEY_ROWID, DATABASE_TABLE, TAG left, because they are most relevant to the NotesDbAdapter .
There is no open() method in the new class. Let's transfer logic of this method to DatabaseHelper .
Now we need to get SQLiteDatabase mDb . Obviously, DatabaseHelper should do this. To teach him to do this, we implement the com.google.inject.Provider interface, which, using the get() method, returns one or another created object.
In addition, we inject the application context into the constructor.
As a result, the DatabaseHelper class will look like this:
 @Singleton public class DatabaseHelper extends SQLiteOpenHelper implements Provider<SQLiteDatabase>{ private static final String TAG = "DatabaseHelper"; private static final String DATABASE_CREATE = "create table notes (_id integer primary key autoincrement, " + "title text not null, body text not null);"; private static final String DATABASE_NAME = "data"; private static final int DATABASE_VERSION = 2; private SQLiteDatabase mDb; @Inject public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } public SQLiteDatabase get() { if (mDb == null || !mDb.isOpen()) { mDb = getWritableDatabase(); } return mDb; } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(DATABASE_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { ... } } 

Now let's point out RoboGuice that the SQLiteDatabase class SQLiteDatabase provided by the provider class DatabaseHelper . To do this, create a configuration class, the so-called. module, and register it in the system (in NotepadApplication )
 public class NotepadModule extends AbstractAndroidModule { @Override protected void configure() { bind(SQLiteDatabase.class).toProvider(DatabaseHelper.class); } } 

 public class NotepadApplication extends RoboApplication { @Override protected void addApplicationModules(List<Module> modules) { modules.add(new NotepadModule()); } } 

We start, we try - it works!

The NotepadModule.configure() method can be extended to bind other types, for example, substitutions for specific interface implementations:
  protected void configure() { bind(SQLiteDatabase.class).toProvider(DatabaseHelper.class); bind(FilterDao.class).to(FilterDaoHttp.class); bind(ItemDao.class).annotatedWith(Http.class).to(ItemDaoHttp.class); } 

In other words, all types of binding are available from Google Guice .

It is worth adding a few words and tests. RoboGuice contains several classes to facilitate and speed up unit-testing. RoboUnitTestCase provides testing for classes that are not dependent on the Android ecosystem. RoboActivityUnitTestCase allows RoboActivityUnitTestCase to run unit tests for Activities. In the future I would like to write a separate article about testing under Android, with a comparison of different approaches and libraries. Testing under Android is still not as well disassembled on Habré as the development itself.

Final diagnosis


So, we make the final diagnosis about the benefits of injections:
+ Reducing the code responsible for initializing objects and managing the life cycle
+ More code responsible for business logic.
+ Reduced object connectivity
+ Minimum space for dependency configuration and object initialization
+ Ease of use and substitution of system objects and resources (Context, Apllication, SharedPreferences, Resources, etc.)
+ As a result, accelerating development and reducing errors

- Additional library increases the size of the application.
Controversial minus. The size of guice-2.0-no_aop.jar and roboguice-1.1.1.jar together is about 545 Kb, which is quite a lot for simple applications, but it is quite acceptable for large and complex applications.
- Decreased performance
Also controversial. Undoubtedly, injections require some time. However, the time spent on this is small and mostly falls at the time of launching the application. If you do not use injections in cycles and animations, the user will not notice the change in performance. Moreover, the performance of smartphones and DalvikVM is constantly growing.

My personal diagnosis: RoboGuice should be used in their projects. The pros definitely outweigh the cons.

Source: https://habr.com/ru/post/116945/


All Articles