Question

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!

Was it helpful?

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>

OTHER TIPS

I've been working on this for some time now. There is one workaround which I find entirely inelegant. I can just use the xml layout to build a DialogFragment by making changes in the Activity:

class HCDialog extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        LayoutInflater inflater = getActivity().getLayoutInflater();
        View view = inflater.inflate(R.layout.dialog_host_credentials, null);
        builder.setView(view);
        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                // TODO Auto-generated method stub

            }
        })
        .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                // TODO Auto-generated method stub

            }
        });
        builder.setTitle(R.string.dialog_title);
        return builder.create();
    }
}

The DialogFragment can be launched from a button like this (case statement is from the code pasted as part my original question):

    case R.id.testing:
        FragmentManager fm = getFragmentManager();
        HCDialog hack_dialog = new HCDialog();
        hack_dialog.show(fm, null);
        return true;

This works. But to me it seems rather silly and I imagine I'm taking the long way around. Especially because now I'll be coding to handle persistent preference values for two versions of what appears to be the exact same dialog.

Is there a better way?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top