简体   繁体   中英

Android snackbar null object reference

I have a simple login page for my app and the first thing I'm doing is checking for permissions. I have to do this at the very beginning because none of the apps functionality will work without all permissions. If permission is given, the login page proceeds to load and everything is fine. But if permission is denied, I want to show a snackbar with some information and an action button so users can request permission again.

The problem is I get an error if the user denies permission:

FATAL EXCEPTION: main
Process: com.example.debug, PID: 16846
java.lang.RuntimeException: Failure delivering result ResultInfo{who=@android:requestPermissions:, request=10, result=-1, data=Intent { act=android.content.pm.action.REQUEST_PERMISSIONS (has extras) }} to activity {com.example.debug/com.example.LoginActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.view.View.getResources()' on a null object reference at android.app.ActivityThread.deliverResults(ActivityThread.java:3699)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:3742)
at android.app.ActivityThread.-wrap16(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1393)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5422)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.view.View.getResources()' on a null object reference at android.support.design.widget.Snackbar.make(Snackbar.java:234)at com.example.LoginActivity.onRequestPermissionsResult(LoginActivity.java:56) at android.app.Activity.dispatchRequestPermissionsResult(Activity.java:6582)
at android.app.Activity.dispatchActivityResult(Activity.java:6460)
at android.app.ActivityThread.deliverResults(ActivityThread.java:3695)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:3742) 
at android.app.ActivityThread.-wrap16(ActivityThread.java) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1393) 
at android.os.Handler.dispatchMessage(Handler.java:102) 
at android.os.Looper.loop(Looper.java:148) 
at android.app.ActivityThread.main(ActivityThread.java:5422) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 

When users accept permission the login page seems to load correctly. Although for some reason it doesn't check through all of my listed permissions, but I'll save that for another question

Here's the code for the login activity:

public class LoginActivity extends AppCompatActivity {

    public static final String TAG = "LoginActivity";

    CoordinatorLayout coordinatorLayout;

    final String[] PERMISSIONS = {Manifest.permission.WRITE_EXTERNAL_STORAGE,
                                Manifest.permission.READ_EXTERNAL_STORAGE,
                                Manifest.permission.WAKE_LOCK,
                                Manifest.permission.CAMERA};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator_layout_login);

        //If any of the permission have not been granted
        if (!hasPermission(PERMISSIONS[0]) || !hasPermission(PERMISSIONS[1]) ||
                !hasPermission(PERMISSIONS[2]) || !hasPermission(PERMISSIONS[3])){
            Log.i(TAG, "checking initial permissions");
            ActivityCompat.requestPermissions(this, PERMISSIONS, 10);
        }
    }

    public boolean hasPermission(String permission){
        Log.i(TAG, "hasPermission()");
        return (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        Log.i(TAG, "onRequestPermissionsResult");
        //If at least one permission has been denied
        if (grantResults.length > 0 && grantResults[0] == -1){
            Log.i(TAG, "granting failed - show snackbar");
            Snackbar.make(coordinatorLayout, R.string.permission_explain, Snackbar.LENGTH_INDEFINITE)
                    .setAction(R.string.snackbar_request_permission, new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            ActivityCompat.requestPermissions(LoginActivity.this, PERMISSIONS, 10);
                        }
                    })
                    .show();
        } else {
            //If all permissions have been granted
            Log.i(TAG, "Permissions granted");
            setContentView(R.layout.activity_login);

            //set username by default
            EditText usernameText = (EditText) findViewById(R.id.input_user);
            //Load some UI things
        }
    }

And if it helps, here is my XML file containing the coordinator layout:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/coordinator_layout_login"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.example.LoginActivity"
    android:background="@color/colorPrimary">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="56dp"
        android:paddingLeft="24dp"
        android:paddingRight="24dp"
        android:descendantFocusability="beforeDescendants"
        android:focusableInTouchMode="true">

        <ImageView android:src="@drawable/ic_run_white_48dp"
            android:layout_width="wrap_content"
            android:layout_height="72dp"
            android:layout_marginBottom="24dp"
            android:layout_gravity="center_horizontal" />

        <!-- Username Label -->
        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp">
            <EditText android:id="@+id/input_user"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="text"
                android:hint="@string/hint_username" />
        </android.support.design.widget.TextInputLayout>

        <!-- Password Label -->
        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp">
            <EditText android:id="@+id/input_password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPassword"
                android:hint="@string/hint_password"/>
        </android.support.design.widget.TextInputLayout>

        <android.support.v7.widget.AppCompatButton
            android:id="@+id/btn_login"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="24dp"
            android:layout_marginBottom="24dp"
            android:padding="12dp"
            android:text="@string/login_button_text"
            android:textColor="@color/colorPrimaryLight"
            android:background="@color/colorPrimaryDark"
            android:onClick="authenticateUser"/>

    </LinearLayout>
</android.support.design.widget.CoordinatorLayout>

I'm thinking the problem has something to do with my coordinator layout in conjunction with my snackbar? I've been staring at this for hours now and no closer to understanding whats wrong

I think there's error in onCreate or in onRequestPermissionResult. You try to find view coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator_layout_login); and that's returning null. Next, you use it in onRequestPermissionResult.

Solutions:

1. Main view

You can use another view instead of non existing coordinatorLayout. In code:

Snackbar.make(coordinatorLayout, R.string.permission_explain, Snackbar.LENGTH_INDEFINITE)
                .setAction(R.string.snackbar_request_permission, new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        ActivityCompat.requestPermissions(LoginActivity.this, PERMISSIONS, 10);
                    }
                })
                .show();

you could use findViewById(android.R.id.content) instead of coordinatorLayout(which is null). The final code will be:

Snackbar.make(findViewById(android.R.id.content), R.string.permission_explain, Snackbar.LENGTH_INDEFINITE)
                    .setAction(R.string.snackbar_request_permission, new View.OnClickListener() {
                         @Override
                         public void onClick(View view) {
                             ActivityCompat.requestPermissions(LoginActivity.this, PERMISSIONS, 10);
                         }
                    })
                    .show();

2. Set content view before looking for coordinatorLayout

In onCreate you have findViewById before setting any content. You could for example move line

setContentView(R.layout.activity_login);
from onRequestPermissionsResult to onCreate before findViewById so onCreate method will be:

 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator_layout_login); //If any of the permission have not been granted if (!hasPermission(PERMISSIONS[0]) || !hasPermission(PERMISSIONS[1]) || !hasPermission(PERMISSIONS[2]) || !hasPermission(PERMISSIONS[3])){ Log.i(TAG, "checking initial permissions"); ActivityCompat.requestPermissions(this, PERMISSIONS, 10); } } 

I guess this is giving to you NullPointerException since you are doing this :

coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator_layout_login);

but you are not declared the setContentView() so it will return NullPointerException because doesn't find that id , try instead of use coordinatorLayout on this case Snackbar.make(coordinatorLayout, R.string.permission_explain, Snackbar.LENGTH_INDEFINITE) try using something like getWindow().getDecorView().getRootView() .

Since you are asking for four permissions shouldn't you check all results in:

//If at least one permission has been denied
if (grantResults.length > 0 && grantResults[0] == -1){

instead of checking the first one?

Just use:

public boolean arePermissionsGranted(int[] grantResults) {
    for (int result : grantResults) {
        if (result != PackageManager.PERMISSION_GRANTED) {
            return false;
        }
    }
    return true;
}

You could also use constants from PackageManager class for result codes:

PackageManager.PERMISSION_GRANTED
PackageManager.PERMISSION_DENIED

http://developer.android.com/training/permissions/requesting.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM