Dimitry Ivanov

public static void main for Android

by DIMITRY IVANOV

Sometimes I find myself wanting to evaluate some Android code without launching an app on a device or an emulator (and without adding log statements in random components). For example Java has a public static void main method that can be added in any class and launched right from the IDE. Can this be done for Android?

public class MainActivity extends Activity {

    public static void main(String[] args) {
        final String input = "tel:+34 666 55 52 22";
        System.out.printf("input: %s, uri: %s%n", input, Uri.parse(input));
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Unfortunately this will fail with somewhat famous exception:

Exception in thread "main" java.lang.RuntimeException: Stub!
	at android.net.Uri.parse(Uri.java:293)
	at io.noties.psvm.MainActivity.main(MainActivity.java:11)

This happens because our code is compiled with android.jar dependency (that can be found in $ANDROID_SDK/platforms/android-${version}/android.jar) that has only public API definitions. All API method signatures are present but their bodies contain a single statement: throw new RuntimeException("Stub!"). android.jar here is a runtime dependency. We still can operate on public API during development, but actual implementation is provided by a device or an emulator when an app is launched.

I labeled the java.lang.RuntimeException: Stub! as somewhat famous because you might've seen it whilst trying to launch java unit tests that contain Android code. This problem is long gone with the help of amazing Robolectric testing framework. But wait... this must mean that Robolectric has a proper android.jar with all the methods present. Can we use it?

It turns out that we can. We should substitute the default mocked android.jar with the one that comes with Robolectric. Add this to your build.gradle:

dependencies {
    debugCompileOnly 'org.robolectric:android-all:9-robolectric-4913185-2'
}

You can find different versions of android-all artifact on Robolectric maven page. As you might've noticed artifact version contains Android platform version in it, so 9-robolectric-4913185-2 would mean Android Pie (9). You can go with any Android version down to 4.1.2_r1-robolectric-0 (Jelly Bean!) which can be helpful if you want to evaluate code on a specific platform.

We've used the debugCompileOnly when adding Robolectric dependency so we do not actually introduce a compile-time dependency on full android.jar. But the good thing is — Intellij still allows to bundle runtime dependencies when launched from the main method.

With that in place let's launch our main method again, click the green play icon to the left of your main method definition:

launch

And whoops:

Exception in thread "main" java.lang.RuntimeException: Stub!
	at android.net.Uri.parse(Uri.java:293)
	at io.noties.psvm.MainActivity.main(MainActivity.java:11)

There is actually another step that is required in order to launch the main method with Robolectric android.jar. After you have clicked the launch button, IDE created a launch configuration which we need to modify. Expand the configurations menu and click Edit Configurations...

edit configurations

Then,

  • Make sure Include dependencies with "Provided" scope is checked
  • Then locate the JRE menu option and expand it
  • Select appropriate Java version (instead of Android API...)

edit configurations

Apply the changes to the configuration and close the window. Now, with that in place we finally can launch the main method.

input: tel:+34 666 55 52 22, uri: tel:+34 666 55 52 22

Process finished with exit code 0

The good thing is this does not interfere with your Robolectric unit tests and both can be used inside a single module. Another good thing is this allows you to evaluate code that contain Android specifics without launching it on a device or an emulator.


< Previous Google Pixel
Next > EmotionLayout