Search code examples
androidnullpointerexceptiongoogle-cast

NullPointerException when calling super.onCreateView() in MiniControllerFragment


In my app I'm trying to implement a MiniControlFragment which is shown when the user decides to close ExpandedControlsActivity (a full-screen version of Mini controller). Instead of adding it declaratively to the xml layout I need to do that programmatically.

So I created own class which extends MiniControllerFragment:

public class CastControllingFragment extends MiniControllerFragment
{
    @Override
    public View onCreateView(LayoutInflater layoutInflater, ViewGroup viewGroup, Bundle bundle)
    {
        View view = super.onCreateView(layoutInflater, viewGroup, bundle);
        if (view != null) {
            FrameLayout.LayoutParams frameLayoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            view.setLayoutParams(frameLayoutParams);
        }
        return view;
    }

    public static void show(FragmentActivity activity) {
        FragmentManager fragmentManager = activity.getSupportFragmentManager();
        if (fragmentManager.findFragmentById(R.id.castMiniController) == null)
        {
            View rootView = activity.findViewById(R.id.fragment_container);
            if (rootView instanceof ViewGroup)
            {                    
               fragmentManager.beginTransaction().add(R.id.fragment_container, new CastControllingFragment()).commit();
            }
        }
    }

}

I instantiate it and add it to the fragment transaction in Activity's onResume callback:

    @Override
    protected void onResume() {
        CastControllingFragment.show(this);
        super.onResume();
    }

This is the Activity's layout with FrameLayout as container for my fragment:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </FrameLayout>

</RelativeLayout>

But, when the super.onCreateView() in my CastControllingFragment is called then I get the following error:

java.lang.RuntimeException: Unable to resume activity java.lang.NullPointerException: Attempt to read from null array at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3400) at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713) at android.app.ActivityThread.-wrap12(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6077) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)

Caused by: java.lang.NullPointerException: Attempt to read from null array at com.google.android.gms.cast.framework.media.widget.MiniControllerFragment.zza(Unknown Source) at com.google.android.gms.cast.framework.media.widget.MiniControllerFragment.onCreateView(Unknown Source) at com.google.sample.cast.refplayer.CastControllingFragment.onCreateView(CastControllingFragment.java:22) at android.support.v4.app.Fragment.performCreateView(Fragment.java:2087) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1113) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1295) at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:801) at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1682) at android.support.v4.app.FragmentController.execPendingActions(FragmentController.java:388) at android.support.v4.app.FragmentActivity.onPostResume(FragmentActivity.java:499) at android.support.v7.app.AppCompatActivity.onPostResume(AppCompatActivity.java:175) at android.app.Activity.performResume(Activity.java:6792) at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3377) at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)  at android.app.ActivityThread.-wrap12(ActivityThread.java)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)  at android.os.Handler.dispatchMessage(Handler.java:102)  at android.os.Looper.loop(Looper.java:154)  at android.app.ActivityThread.main(ActivityThread.java:6077)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)


Solution

  • There was a change in the latest Cast SDK that made it incompatible (crashing) with older versions of Google Play Services. Unfortunately, even cast sample app crashes when using latest Cast SDK with outdated GPS (or on emulators). The issue has been discussed here: https://github.com/googlecast/CastVideos-android/issues/12

    The solution is to check Google Play Services version before initialising any cast components, including mini controller (i.e. you can't just put mini controller fragment into your xml layout file - you either have to inflate it dynamically, or have two layout files - one with, and one without your mini controller).

    Code to check GPS version:

    GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
    int resultCode = apiAvailability.isGooglePlayServicesAvailable(context);
    isGPSAvailable = (resultCode == ConnectionResult.SUCCESS);
    

    If the results is not ConnectionResult.SUCCESS, don't initialise your MiniControllerFragment.

    Also, please keep in mind that it is not possible to instantiate MiniControllerFragment using new MiniControllerFragment(). You have to inflate it from xml or you will get NullPointerException.

    There are two ways to inflate MiniControllerFragment:

    1. Create separate xml layout files and inflate appropriate one in your Activity.onCreate:

      setContentView(isGPSAvailable ? R.layout.activity_main_with_controller : R.layout.activity_main_without_controller);
      
    2. Create ViewStub in your layout pointing to MiniControllerFragment and inflate it only when you have play services.

    activity layout:

    <ViewStub
    android:id="@+id/cast_minicontroller"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout="@layout/cast_mini_controller_fragment"
    />
    

    cast_mini_controller_fragment:

    <?xml version="1.0" encoding="utf-8"?>
    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/castMiniController"
              class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:visibility="gone"/>
    

    code in your activity onCreate():

    ViewStub miniControllerStub = (ViewStub) findViewById(R.id.cast_minicontroller);
    if (isGPSAvailable) {
        miniControllerStub.inflate();
    }
    

    I prefer the ViewStub approach as it does not duplicate your layouts.