Data transfer in android happens a lot via intents. Even in case of single activity pattern (to which square is a strong proponent of) there are still Android's system activities that are needed to be started or passing messages to services. So intents are probably going to stay for a while and if there's a way to make them better it's worth taking a look at.
-
Type-safety One of the advantages Intents offer is the completely decoupling of the calling code and the recieving activity. It has one major caveat that compilers are now clueless whether the passed in values are correct. Any key can be mapped with any of the types that can be put into the intent.
-
Definition There's no standard way to specify intent requirements. Some activities need some additional extras that are required for it to start. In some cases additional flags or action might have to be set. There should be an easier and straight forward way of defining these requirements such that misuse can be avoided or spotted on the calling side (compile time errors or fail-fast: during execution of calling code)
These happen to be the major problems with intents.
The ideal solution for this problem is to write wrappers for all intents and expose helpers/builders for starting activities. But the amount of code actually needed for this is very huge. A sample DemoActivity
with three extras two of which are definitely required(id
and name
) will look as follows:
public class DemoActivity extends Activity {
private static final String ID = "DemoActivity#id";
private static final String NAME = "DemoActivity#name";
private static final String GENDER = "DemoActivity#gender";
private static class Parser {
private final Intent intent;
private Parser(Intent intent) {
this.intent = intent;
}
private int id(int defValue) {
if (intent == null) return defValue;
return intent.getIntExtra(ID, defValue);
}
private int name() {
if (intent == null) return null;
return intent.getStringExtra(NAME);
}
// @TypeDef support annotation
@Gender
private int gender(int defValue) {
if (intent == null) return defValue;
return intent.getIntExtra(GENDER, defValue);
}
}
public static class Builder {
private final int id;
private final String name;
@Gender
private int gender;
public Builder(int id, String name) {
this.id = id;
this.name = name;
}
public Builder gender(@Gender int g) {
this.gender = g;
return this;
}
public Intent intent(Context context) {
return new Intent(context, DemoActivity.class)
.putExtra(ID, id)
.putExtra(NAME, name)
.putExtra(GENDER, gender);
}
public void start(Context context) {
context.startActivity(intent(context));
}
}
}
As can be seen this quickly gets verbose with null checks and changing one field leads to additional changes in multiple places, maintainability becomes hard.
The repeated code in the above can be generated by an annotation processor. Opting for a definition structure very similar to AutoValue
. The code could look like:
public class DemoActivity extends Activity {
@Override
protected void onCreate(Bundle savedState) {
// DemoActivity_Extras is the generated class
Extras extras = DemoActivity_Extras.from(this);
}
public static Builder withExtras(int id, String name) {
// DemoActivity_Builder is the generated class
// all non-optional parameters are taken in constructor
return new DemoActivity_Builder(id, name);
}
// generated DemoActivity_Extras
@Args(DemoActivity.class)
static abstract class Extras {
protected abstract int id();
protected abstract String name();
@Optional @Gender
protected abstract int gender(int defValue);
// methods to check if the extra was passed in the intent
@Helpers
protected abstract boolean hasId();
protected abstract boolean hasName();
protected abstract boolean hasGender();
}
// This is optional
// the info above given by @Extras is enough
// to generate DemoActivity_Builder
@ArgFactory(DemoActivity.class)
interface Builder {
Builder gender(@Gender int gender);
Intent intent(Context context);
void start(Context context);
void startActivityForResult(Context context, int requestCode);
}
}
Now this activity can be started from the calling code in a very clean way:
DemoActivity.withExtras(1, "Name")
.gender(1)
.start();
Compiler knows what the activity needs to start now. We get awesome autocompletion and the completion would suggest us the required fields and the optional ones. Notice the absence of intent key definitions. The keys can also be generated and if some of the extras need to have a partcular key, it can be provided with a @Key
annotation.
The same method can be used for generating Builder
s for activities that use Intents with uris or action and data. The code in the above class can further be reduced. Once the optional parameters are declared with @Optional
the Builder
can actually be generated without the interface.
There is no need for both @Args
and @ArgFactory
to be defined as inner classes of Activity. So these annotations could be used for generating type safe wrappers for any existing activities (from other libraries or core android activities).
I have already made an attempt in this direction once and made the Bundler library. It's actually simpler to use but it has many issues that I discovered while using and it heavily exposes the generated classes which turned out to be a very bad idea (no surprises there).
I'd like inputs and would appreciate any advice and suggestions in re-doing the library (or starting a new one) with a better api and more focus.