javaandroidpreferencesdialog-preference

Cannot launch DialogPreference programmatically (Null Pointer Exception thrown)


I have a custom class that extends DialogPreference. It works perfectly if launched from the Preference menu. I want to be able to launch it from an Activity as well. Below is my DialogPreference class in which I exposed the showDialog() method suggested by this thread. When I call it I get a Null Pointer Exception but haven't been able to figure out why.

The error is thrown at line 27 which is in onBindDialogView() where hText.setText() is called.

package com.jumptuck.recipebrowser;

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.EditText;

// Pop-up dialog used to set and modify host and login credentials
public class HostCredentialsDialogPreference extends DialogPreference {
    static final String TAG = "HostCredentialsDialogPreference";
    EditText hText, uText, pText;

    public HostCredentialsDialogPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
        setDialogLayoutResource(R.layout.dialog_host_credentials);
    }

    @Override
    protected void onBindDialogView(View view) {
        super.onBindDialogView(view);
        Log.d(TAG,"onBindDialogView");

        SharedPreferences sp = getSharedPreferences();
        hText.setText(sp.getString("host", ""));
        uText.setText(sp.getString("username", ""));
        pText.setText(sp.getString("password", ""));
    }

    @Override
    protected View onCreateDialogView() {
        // Guide for this technique found at:
        // http://alexfu.tumblr.com/post/23683149440/android-dev-custom-dialogpreference
        Log.d(TAG,"onCreateDialogView");
        View root = super.onCreateDialogView();
        hText = (EditText) root.findViewById(R.id.host);
        uText = (EditText) root.findViewById(R.id.username);
        pText = (EditText) root.findViewById(R.id.password);
        return root;
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        super.onDialogClosed(positiveResult);
        if (positiveResult){
            Log.d(TAG,"Clicked Save");
            SharedPreferences sp = getSharedPreferences();
            SharedPreferences.Editor editor = sp.edit();
            editor.putString("host", hText.getText().toString());
            editor.putString("username", uText.getText().toString());
            editor.putString("password", pText.getText().toString());
            editor.commit();
        }
        else {
            Log.d(TAG,"Clicked Cancel");
        }
    }

    void show() {
        showDialog(null);
    }

}

I'm using a "Testing" button try to launch the dialog from another Activity:

public boolean onOptionsItemSelected(MenuItem item) {
    Log.d(TAG, "onOptionsItemSelected");

    switch (item.getItemId()) {
    case R.id.item_prefs:
        startActivity(new Intent(this, PrefsActivity.class));
        return true;
    case R.id.refresh:
        if (credentialsExist()) {
            refreshListView();
        }
        return true;
    case R.id.recipe_dir:
        startActivity(new Intent(this, RecipeDisplayActivity.class));
        return true;
    case R.id.testing:
        HostCredentialsDialogPreference hc = new HostCredentialsDialogPreference(this, null);
        hc.show();
        return true;

    default:
        return false;
    }
}

Here are the xml files for my Preference and the Dialog Preference:

<?xml version="1.0" encoding="utf-8"?>

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
    <com.jumptuck.recipebrowser.HostCredentialsDialogPreference
        android:key="dialog_credentials"
        android:title="Server Address and Login"
        android:summary="Set Host, Username and Password" />

</PreferenceScreen>

and

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/dialog_hostname_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/dialog_hint_uri" />

    <EditText
        android:id="@+id/host"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textUri" />

    <TextView
        android:id="@+id/dialog_username_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/dialog_hint_user" />

    <EditText
        android:id="@+id/username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text" />

    <TextView
        android:id="@+id/dialog_password_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/dialog_hint_password" />

    <EditText
        android:id="@+id/password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fontFamily="sans-serif"
        android:inputType="textPassword" />

</LinearLayout>

And finally the Logcat:

D/RecipeListActivity( 5894): onOptionsItemSelected
D/HostCredentialsDialogPreference( 5894): onCreateDialogView
D/HostCredentialsDialogPreference( 5894): onBindDialogView
D/AndroidRuntime( 5894): Shutting down VM
W/dalvikvm( 5894): threadid=1: thread exiting with uncaught exception (group=0x40a13300)
E/AndroidRuntime( 5894): FATAL EXCEPTION: main
E/AndroidRuntime( 5894): java.lang.NullPointerException
E/AndroidRuntime( 5894):    at com.jumptuck.recipebrowser.HostCredentialsDialogPreference.onBindDialogView(HostCredentialsDialogPreference.java:27)
E/AndroidRuntime( 5894):    at android.preference.DialogPreference.showDialog(DialogPreference.java:289)
E/AndroidRuntime( 5894):    at com.jumptuck.recipebrowser.HostCredentialsDialogPreference.show(HostCredentialsDialogPreference.java:62)
E/AndroidRuntime( 5894):    at com.jumptuck.recipebrowser.RecipeListActivity.onOptionsItemSelected(RecipeListActivity.java:192)
E/AndroidRuntime( 5894):    at android.app.Activity.onMenuItemSelected(Activity.java:2534)
E/AndroidRuntime( 5894):    at com.android.internal.policy.impl.PhoneWindow.onMenuItemSelected(PhoneWindow.java:958)
E/AndroidRuntime( 5894):    at com.android.internal.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:735)
E/AndroidRuntime( 5894):    at com.android.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:149)
E/AndroidRuntime( 5894):    at com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:874)
E/AndroidRuntime( 5894):    at com.android.internal.view.menu.ListMenuPresenter.onItemClick(ListMenuPresenter.java:166)
E/AndroidRuntime( 5894):    at android.widget.AdapterView.performItemClick(AdapterView.java:298)
E/AndroidRuntime( 5894):    at android.widget.AbsListView.performItemClick(AbsListView.java:1086)
E/AndroidRuntime( 5894):    at android.widget.AbsListView$PerformClick.run(AbsListView.java:2859)
E/AndroidRuntime( 5894):    at android.widget.AbsListView$1.run(AbsListView.java:3533)
E/AndroidRuntime( 5894):    at android.os.Handler.handleCallback(Handler.java:615)
E/AndroidRuntime( 5894):    at android.os.Handler.dispatchMessage(Handler.java:92)
E/AndroidRuntime( 5894):    at android.os.Looper.loop(Looper.java:137)
E/AndroidRuntime( 5894):    at android.app.ActivityThread.main(ActivityThread.java:4745)
E/AndroidRuntime( 5894):    at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime( 5894):    at java.lang.reflect.Method.invoke(Method.java:511)
E/AndroidRuntime( 5894):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
E/AndroidRuntime( 5894):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
E/AndroidRuntime( 5894):    at dalvik.system.NativeStart.main(Native Method)
W/ActivityManager(  148):   Force finishing activity com.jumptuck.recipebrowser/.RecipeListActivity
W/WindowManager(  148): Failure taking screenshot for (246x410) to layer 21025
W/ActivityManager(  148): Activity pause timeout for ActivityRecord{412097e0 com.jumptuck.recipebrowser/.RecipeListActivity}
I/Choreographer(  281): Skipped 39 frames!  The application may be doing too much work on its main thread.
W/ActivityManager(  148): Activity destroy timeout for ActivityRecord{412097e0 com.jumptuck.recipebrowser/.RecipeListActivity}

Anyone idea what I'm doing wrong? Thanks!


Solution

  • The best answer can be found here but I think it needs just a bit of clarification because that answer wrongly suggests two different style declarations for the manifest.

    If you want to launch one dialog from an Activity and still be able to launch it form a Preference you just need to create an Activity that launches the Dialog. That Activity can then be launched as an intent in the Preference XML or from another Activity. The trick comes in how you style it. You want to style the Activity as a Dialog. This way the dialog that your Activity launches will looks right. The side effect of this approach is that a floating Action Bar will be show in the middle of the screen behind your Dialog. The fix for that is to use a Dialog style with no ActionBar. I'm using Holo.Light theme so I put this in my AndroidManifest

    <activity android:name=".DemoDialogActivity"
                android:theme="@android:style/Theme.Holo.Light.Dialog.NoActionBar" />
    

    The other part of the puzzle is to make sure you call finish(); when you're done (It's the last thing I did in the OnClickListener for both of my buttons). If you don't, the dialog will close but the Activity will still be open, leaving a small blank rectangle in the middle of a darkened screen.

    Here's a working example of the Activity:

    import android.app.Activity;
    import android.app.AlertDialog;
    import android.content.DialogInterface;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    
    public class DemoDialogActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            LayoutInflater lf = LayoutInflater.from(this);
            // This adds XML elements as a custom view (optional):
            final View customElementsView = lf.inflate(
                    R.layout.activity_credentials, null);
            AlertDialog alert = new AlertDialog.Builder(this)
                    // This adds the custom view to the Dialog (optional):
                    .setView(customElementsView)
                    .setTitle("This is the Title")
                    .setMessage("This is the AlertDialog message (optional)")
                    .setNegativeButton("Cancel",
                            new DialogInterface.OnClickListener() {
    
                                @Override
                                public void onClick(DialogInterface dialog,
                                        int which) {
                                    // Cancel was clicked; do something
                                    // Close Activity
                                    finish();
                                }
                            })
                    .setPositiveButton("OK", new DialogInterface.OnClickListener() {
    
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            // OK was clicked; do something
                            // Close Activity
                            finish();
                        }
                    }).create();
    
            // Show the dialog
            alert.show();
        }
    }
    

    Launch it programmatically:

    Intent launch_dialog = new Intent(getApplicationContext(),
        DemoDialogActivity.class);
    startActivity(launch_dialog);
    

    Or as a Preference in XML:

    <Preference
        android:key="demo_dialog"
        android:title="Title of item on Prefs screen"
        android:summary="This will be small text below the title">
        <intent
            android:action="android.intent.action.VIEW"
            android:targetClass="com.example.package.DemoDialogActivity"
            android:targetPackage="com.example.package" />
    </Preference>