Way to ActivityStarter

Marcin Moskala
4 min readNov 13, 2017

--

The story started about one year ago, when I was lying down on the couch and thinking. My thoughts were flowing around subject of Android maturity. I had a feeling that back-end frameworks are far more mature than Android and iOS SDKs. It is possibly true, while they exist for far longer and they had much more attempts (different frameworks which were checking out different ideas). But then I switched into auto-reflection mode: “If I think that Android is primitive, then I should be able to point some elements that might be improved”. The first problem I pointed out was how we start Activities. This all Intent creation and Activity starting is definitely not how we should do it:

public static Intent getIntent(Context context, User user) {
Intent intent = new Intent(context, UserActivity.class);
intent.putExtra("com.mm.UserActivity.UserKey", user);
return intent;
}

public static void start(Context context, User user) {
Intent intent = getIntent(context, user);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
user = getIntent().getParcelable("com.mm.UserActivity.UserKey")
}

Such an amount of boilerplate should not be needed in every Activity. But how could it be implemented instead? My first thought was that arguments should be passed via the constructor:

class UserActivity extends Activity {
User user;
UserActivity(User user) {
this.user = user;
}
}
startActivity(new UserActivity(user));

Then I realized that it is not a case where we should use the constructor — it is the case for a static factory method (read more about it on the Effective Java book). The problem with the previous approach was not how we start Activities. The problem was that we need to define this static factory methods for every Activity. This is, I realized, something I can fix.

My first idea was to use AspectJ to automatically fill the body of static functions. Soon I realized how ineffective it would be. Instead I figured out that I can effectively use annotation processing to generate all this static functions in a separate class:

public class UserActivity extends Activity {

@Arg User user;
}
// Generated class
public final class UserActivityStarter {
public static Intent getIntent(Context context, User user) {
Intent intent = new Intent(context, UserActivity.class);
intent.putExtra("com.mm.UserActivity.UserKey", user);
return intent;
}

public static void start(Context context, User user) {
Intent intent = getIntent(context, user);
context.startActivity(intent);
}
}

Then I realized that in the same way arguments can be injected into Activities:

public class UserActivity extends Activity {

@Arg User user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityStarter.fill(this, savedInstanceState);
}
}
// Generated class
public final class UserActivityStarter {

public static void fill(MainActivity activity, Bundle savedInstanceState) {
Intent intent = activity.getIntent();
if (intent.hasExtra("..."))
activity.name = intent.getStringExtra("...");
}
}
}

This is how ActivityStarter was born :)

I started supporting Activites, Fragments, Services and BroadcastReceivers. People started using it and asking for other features. Soon I added support for:

Soon I had my own ambition — I am proud Kotlin user and following library wasn’t designed for Kotlin. It worked with Kotlin since its first version, but with argument properties that are:

  • lateinit or nullable
  • mutable (read-write)

It wasn’t so good while we often want non-nullable read-only properties. This was a reason why I added special support for Kotlin which injects arguments lazily in property delegate:

class StudentDataActivity : BaseActivity() {

@get:Arg(optional = true) var name: String by argExtra()
@get:Arg(optional = true) val id: Int by argExtra(defaultId)
@get:Arg var grade: Char by argExtra()
@get:Arg val passing: Boolean by argExtra()
@get:Arg val user: User by argExtra()
}

After all I am satisfied with the state of the library. It helps me in every project I am using. I believe that it also helps others because I see on downloads counter that people are using it. This all make me satisfied — I feel that I made something easier for me and others. That makes me more proud than even projects for big money :)

If you think that this is important, share it with others. To mention me on Twitter, use @MarcinMoskala.

If you need some help then remember that I am open for consultations.

To follow me, use the button below. If you like it, remember to clap. Note that if you hold the clap button, you can leave more claps.

--

--

Marcin Moskala

Kt. Academy creator, co-author of Android Development with Kotlin, author of open-source libraries, community activist. http://marcinmoskala.com/