Smarter ideas worth writing about.

Android Autowire Library Part 2

Dealing with Android boilerplate code when creating Activities, Fragments, and Views can be more than a little annoying.  This was the subject of an earlier blog where I used Java Annotations to “autowire” Android views rather than the findViewById() and typecast that is expected for Android applications.  This approach was a good start, but it was not enough.

Most large Android applications are fairly complex with Fragments, custom views, inheritance, and even nested Fragments.  Simply making it slightly easier to load Views into an Activity from the XML is not very helpful for these more complex design patterns.  So now the library has been expanded to be useful in more situations, and make more boilerplate code disappear.

Before we could autowire views by annotation, but now that ability has been given not just to Activities, but also to Fragments and custom views.  See the example below for a custom view.

public class CustomView extends RelativeLayout{
	
	@AndroidView
	private TextView title;

	@AndroidView
	private ImageView icon;

	public CustomView(Context context, AttributeSet attrs, int defStyle){
		super(context, attrs, defStyle);
		LayoutInflater inflater = LayoutInflater.from(context);
		Inflater.inflate(R.layout.custom_view, this);
		AndroidAutowire.autowireView(this, CustomeView.class, context);
	}
}

 

A new annotation has been added called @AndroidLayout to take the place of the setContentView() method.  The setContentView() method may not take much time to write, but it has always felt a little odd.  It goes in the onCreate() method and usually tightly couples the Activity with a layout XML file. Moving this to a Class (or Type) annotation does a better job expressing this contract between the XML and the Activity, and is slightly cleaner to read.  But we get the biggest benefit from this new annotation when using it with Fragments.  By using this annotation with a Fragment class, you can abstract the Fragments layout inflation from the Fragment Implementation class, leaving you with cleaner code and less boilerplate.

Finally, I set out to tackle, in my opinion, one of the more annoying problems with boilerplate Android code: the configuration state saving architecture.  Android uses the onSaveInstanceState() method to give you the opportunity to preserve the state of your Activity before it is removed by the OS and recreated later.  This can happen on rotation, on low memory, or other similar situations. Here is an example of doing this:

public class MainActivity extends BaseActivity{

	private static final String SOME_STATE_KEY = "some_state_key";
	private int someState;

	@Override
	public void onCreate(Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		if(savedInstanceState != null){
			someState = savedInstanceState.getInt(SOME_STATE_KEY);
		}
	}

	@Override
	protected void onSaveInstanceState(Bundle outState){
		super.onSaveInstanceState(outState);
		outState.putInt(SOME_STATE_KEY, someState);  
	}
}

 

This is a very simple example, and it can get much more complex than this, especially when you start considering Serializable objects which have to be read from the bundle in onCreate() after being saved in onSaveInstanceState(). Let’s take a look at what this same class looks like using the annotation-based approach.

@AndroidLayout(R.layout.main)
public class MainActivity extends BaseActivity{
	@SaveInstance
	private int someState

	@Override
	public void onCreate(Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
	}
}

 

This has a much cleaner look to it (I also threw the @AndroidLayout in there for good measure). Of course the meat of the work still has to be done, and in this case it is done in the base class using the AndroidAutowire library code.

public class BaseActivity extends Activity{
	
	@Override
	protected void onCreate(Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
		AndroidAutowire.loadFieldsFromBundle(savedInstanceState, 
			this, BaseActivity.class);
	}

	@Override
	protected void onSaveInstanceState(Bundle outState){
		super.onSaveInstanceState(outState);
		AndroidAutowire.saveFieldsToBundle(outState, 
			this, BaseActivity.class);
	}
}

 

Still pretty clean, and it has the added benefit of being out of the way of our MainActivity and the development that needs to go on there. Note that this BaseActivity is simplified and does not contain other details like the @AndroidLayout code since the purpose of this example was to illustrate the use of the @SaveInstance annotation for saving instance state.

Of all the pieces in this library, I think the @SaveInstance annotation saves the most effort and best showcases just how onerous some of the Android boilerplate code can be.  It’s nice to have the library keep track of your key value pairs for you, and you know that the value of the variables you annotate will be there when you need it.  I have tested this code pretty well, and it will work with any primitive type variable and any Serializable object (even objects that have to be cast to Serializable, like java.util.List).

 

Features

  • Supports Inheritance of Activities. You can inherit views from parent Activities, and every view will be picked up and wired in
  • As it uses reflection, it will work with private variables
  • Comes with several out of the box ways of specifying IDs allowing for flexibility in naming IDs and implementing the annotations
  • Provides an optional required field in the annotation, so if an ID is not found, the variable will be skipped without an Exception being thrown
  • Support Annotations for Layout as well as Views
  • Support an Annotation based approach for saving instance state.  This also allows for inheritance.
  • Can be adapted to work with Fragments as well as Activities
  • Can be adapted to work with CustomViews

The AndroidAutowire library is available at: https://github.com/CardinalNow/AndroidAutowire

Share:

About The Author

Senior Consultant
Jacob is an Enterprise Java consultant in Cardinal's Cincinnati office who is currently focused on mobile application development.