Не удается запустить DialogPreference программно (выброшено исключение нулевого указателя)
У меня есть собственный класс, который расширяет DialogPreference. Он отлично работает, если запущен из меню настроек. Я хочу иметь возможность запускать его и из Activity. Ниже представлен мой класс DialogPreference, в котором я представил метод showDialog(), предложенный этим потоком. Когда я звоню, я получаю исключение нулевого указателя, но не могу понять, почему.
Ошибка генерируется в строке 27, которая находится в onBindDialogView(), где вызывается hText.setText().
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);
}
}
Я использую кнопку "Тестирование", попробуйте запустить диалоговое окно из другого занятия:
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;
}
}
Вот XML-файлы для моего предпочтения и предпочтения диалога:
<?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>
а также
<?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>
И наконец 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}
Есть идеи, что я делаю не так? Спасибо!
2 ответа
Лучший ответ может быть найден здесь, но я думаю, что он нуждается в небольшом уточнении, потому что этот ответ ошибочно предлагает два разных объявления стиля для манифеста.
Если вы хотите запустить одно диалоговое окно из действия и все еще иметь возможность запустить его из предпочтения, вам просто нужно создать действие, которое запускает диалог. Затем это действие можно запустить как намерение в предпочтительном XML или из другого действия. Хитрость заключается в том, как вы это делаете. Вы хотите стилизовать активность как диалог. Таким образом, диалог, который запускает ваша активность, будет выглядеть правильно. Побочным эффектом этого подхода является то, что плавающая панель действий будет отображаться в середине экрана за вашим диалогом. Исправление для этого заключается в использовании стиля диалога без Action Bar. Я использую тему Holo.Light, поэтому я поместил ее в мой AndroidManifest
<activity android:name=".DemoDialogActivity"
android:theme="@android:style/Theme.Holo.Light.Dialog.NoActionBar" />
Другая часть головоломки - убедиться, что вы вызываете finish(); когда вы закончите (это последнее, что я сделал в OnClickListener для обеих моих кнопок). Если вы этого не сделаете, диалоговое окно закроется, но действие все равно останется открытым, оставив небольшой пустой прямоугольник в середине затемненного экрана.
Вот рабочий пример действия:
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();
}
}
Запустите его программно:
Intent launch_dialog = new Intent(getApplicationContext(),
DemoDialogActivity.class);
startActivity(launch_dialog);
Или как предпочтение в 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>
Я работал над этим в течение некоторого времени. Есть один обходной путь, который я считаю совершенно не элегантным. Я могу просто использовать макет xml для построения DialogFragment, внеся изменения в 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();
}
}
DialogFragment может быть запущен с помощью кнопки, подобной этой (инструкция case взята из кода, вставленного как часть моего исходного вопроса):
case R.id.testing:
FragmentManager fm = getFragmentManager();
HCDialog hack_dialog = new HCDialog();
hack_dialog.show(fm, null);
return true;
Это работает. Но мне это кажется довольно глупым, и я предполагаю, что я иду далеко. Тем более, что сейчас я буду писать код для обработки постоянных значений предпочтений для двух версий того же диалога.
Есть ли способ лучше?