7

While running Espresso tests using Mockito, I am currently having this issue that "Attempt to read from the field 'int android.content.pm.ApplicationInfo.targetSdkVersion' on a null object reference" exception is randomly appearing after the main Activity was initiated.

This is the init part for the main Activity:

private MainActivity subject;
SingleActivityFactory<MainActivity> activityFactory =
    new SingleActivityFactory<MainActivity>(MainActivity.class) {
        @Override
        protected MainActivity create(Intent intent) {
            subject = spy(getActivityClassToIntercept());
            return subject;
        }
    };

@Rule public ActivityTestRule<MainActivity> mainActivityRule =
    new ActivityTestRule<>(activityFactory, true, false);

@Before
public void init() {
    mainActivityRule.launchActivity(null);
}

This is the exception:

java.lang.NullPointerException: Attempt to read from field 'int 
android.content.pm.ApplicationInfo.targetSdkVersion' on a null object 
reference
at android.view.View.getLayoutDirection(View.java:10277)
at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:446)
at android.view.View.measure(View.java:23169)
at android.support.v4.widget.DrawerLayout.onMeasure(DrawerLayout.java:1059)
at android.view.View.measure(View.java:23169)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6749)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:141)
at android.view.View.measure(View.java:23169)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6749)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535
Test running failed: Instrumentation run failed due to 'Process crashed.'

Also, the logcat of crash:

2018-11-06 15:34:50.391 3299-3299/com.application.android E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.application.android, PID: 3299
java.lang.NullPointerException: Attempt to read from field 'int android.content.pm.ApplicationInfo.targetSdkVersion' on a null object reference
    at android.view.View.getLayoutDirection(View.java:10277)
    at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:446)
    at android.view.View.measure(View.java:23169)
    at android.support.v4.widget.DrawerLayout.onMeasure(DrawerLayout.java:1059)
    at android.view.View.measure(View.java:23169)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6749)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)

The issue here is that this problem is not consistent - failure ration is about 40%. Does anybody have any insights about this issue? Also, I should mention that this issue is not appearing if the activity is launched without spying Activity using Mockito.

Update:

The issue was fixed by introducing changes into View.java class:

@ResolvedLayoutDir
public int getLayoutDirection() {
    //added check if ApplicationInfo is not null
    if(getContext().getApplicationInfo() == null){
            View v = this;
            Log.i("View id: ", getContext().getString(v.getId()));
            Log.e("Main error", "Bug catched");
    }
    else {
        final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
        if (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
            return LAYOUT_DIRECTION_RESOLVED_DEFAULT;
        }
    }
    return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ==
            PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
}

Would it be possible to achieve similar result without modifying the system class?

Tomas March
  • 81
  • 1
  • 4
  • Do you use `getFragmentManager()` method in code? Also @Datenshi – Ori Marko Nov 12 '18 at 13:30
  • Yes I do in a few places – Datenshi Nov 12 '18 at 14:12
  • Please add the solution [as an answer](https://stackoverflow.com/help/self-answer), instead of adding it to the question. You can also [accept your own answer](http://blog.stackoverflow.com/2009/01/accept-your-own-answers/), but you must wait 48 hours to do so. – adiga Nov 26 '18 at 09:01

2 Answers2

3

I think your issue connected to previous issue,

Which produce similar behavior when using getFragmentManager() method,

Solution is:

use getSupportFragmentManager() instead and extend from v4.app.Fragment class

Ori Marko
  • 56,308
  • 23
  • 131
  • 233
  • Sadly, the proposed solution was not sufficient and issue still remains even though all `getFragmentManager()` methods was changed to `getSupportFragmentManager` method. – Tomas March Nov 13 '18 at 14:35
  • @TomasMarch did you replace all usages of `v4.app.Fragment`? – Ori Marko Nov 13 '18 at 14:54
  • did you meant `android.app.Fragment`? I have changed all usages of `android.app.Fragment` to `v4.app.Fragment`, however it didn't changed the outcome. – Tomas March Nov 13 '18 at 15:21
2

most likely the Context is not being set up properly ...

the method in android.view.View actually looks alike this:

public int getLayoutDirection() {
    return mViewFlags & LAYOUT_DIRECTION_MASK;
}

there is nothing which would use targetSdkVersion, but one occurance:

case R.styleable.View_fadingEdge:
    if (context.getApplicationInfo().targetSdkVersion >= ICE_CREAM_SANDWICH) {
        break;
    }

too bad the code of the test in incomplete - most likely the ActivityTestRule is wrongful; the new ActivityTestRule<> looks strange without the <T> and should possibly rather look alike:

new ActivityTestRule<MainActivity>(getActivityFactory(), true, false);

also the mainActivityRule.launchActivity(null); appears useless.

see InterceptingActivitySampleTest.java for a functional example.

just found another example, which is almost the same as the example in the question:

@RunWith(AndroidJUnit4.class)
public class MainActivityTest {

    MainActivity subject;

    SingleActivityFactory<MainActivity> activityFactory = new SingleActivityFactory<MainActivity>(MainActivity.class) {
        @Override
        protected MainActivity create(Intent intent) {
            subject = spy(getActivityClassToIntercept());
            return subject;
        }
    };

    @Rule
    public ActivityTestRule<MainActivity> testRule = new ActivityTestRule<>(activityFactory, true, true);

    @Test
    public void activity_isBeingSpied() {
        verify(subject).setContentView(R.layout.layout_main);
    }
}
Martin Zeitler
  • 1
  • 19
  • 155
  • 216
  • Thanks for the suggestion @Martin. I have updated my test exactly as suggested in your example, however the issue still remains. Maybe you have additional ideas why context had been not initiated correctly? – Tomas March Nov 16 '18 at 13:26
  • @TomasMarch maybe set a break-point at `return subject;` or in other places. I'm rather certain the `Context` is not being provided (based upon the error message). also `android.support.test` and `androidx.test` use different test runners. the stack-traces also seem a little short, because it's only framework classes, not the `MainActivity` or alike. – Martin Zeitler Nov 16 '18 at 13:32
  • 1
    when the error is sporadic, this hints for a race condition being present (not necessarily within the test code). see https://stackoverflow.com/a/39983311/549372 – Martin Zeitler Nov 16 '18 at 13:46