简体   繁体   中英

Android DialogPreference NullPointerException in onRestoreInstanceState

I am trying to implement a DialogPreference with two NumberPicker objects, that restores the last changed NumberPicker values after orientation change:

public class CustomTimePreference extends DialogPreference {

public NumberPicker firstPicker, secondPicker;

private int lastHour = 0;
private int lastMinute = 15;
private int firstMaxValue;
private int tempHour;
private int tempMinute;
private int rotatedHour;
private int rotatedMinute;
private int firstMinValue = 0;
private int secondMinValue=0;
private int secondMaxValue=59;
private String headerText;
private boolean usedForApprox;




public static int getHour(String time){
    String[] pieces = time.split(":");
    return (Integer.parseInt(pieces[0]));
}

public static int getMinute(String time){
    String[] pieces = time.split(":");
    return (Integer.parseInt(pieces[1]));
}

public CustomTimePreference(Context context){
    this(context, null);
}

public CustomTimePreference(Context context, AttributeSet attrs){
    super(context, attrs);
    init(attrs);
    setDialogLayoutResource(R.layout.custom_time_preference);
    setPositiveButtonText(context.getString(R.string.time_preference_set_text));
    setNegativeButtonText(context.getString(R.string.time_preference_cancel_text));
}

private void init(AttributeSet attrs){
    TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CustomTimePreference);
    firstMaxValue = a.getInteger(R.styleable.CustomTimePreference_firstMaxValue,10);
    usedForApprox = a.getBoolean(R.styleable.CustomTimePreference_usedForApproximate, false);
    headerText = a.getString(R.styleable.CustomTimePreference_customTimeDialogTopText);
    a.recycle();
}

public void setFirstPickerValue(int value){
    firstPicker.setValue(value);
}

public void setSecondPickerValue(int value){
    secondPicker.setValue(value);
}



@Override
protected View onCreateDialogView(){
    Log.d("OnCreateDialogView","nanana");
    View root = super.onCreateDialogView();
    TextView tv = (TextView)root.findViewById(R.id.custom_time_preference_title);
    tv.setText(headerText);
    firstPicker = (NumberPicker)root.findViewById(R.id.time_preference_first_picker);
    firstPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {

        @Override
        public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
            // TODO Auto-generated method stub
            tempHour = newVal;
        }
    });

    secondPicker = (NumberPicker)root.findViewById(R.id.time_preference_second_picker);
    secondPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {

        @Override
        public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
            // TODO Auto-generated method stub
            tempMinute = newVal;
        }
    });
    if(usedForApprox){
        int smallestValue = MainActivity.getShortestPeriodLength(getContext());
        int second = smallestValue % 60;
        second-=1;
        firstPicker.setMaxValue(second);
        secondPicker.setMaxValue(59);

    } else {
        firstPicker.setMaxValue(firstMaxValue);
        secondPicker.setMaxValue(secondMaxValue);
    }
    firstPicker.setMinValue(firstMinValue);
    secondPicker.setMinValue(secondMinValue);
    return root;
}

@Override
protected void onBindDialogView(View v){
    super.onBindDialogView(v);
        firstPicker.setValue(lastHour);
        secondPicker.setValue(lastMinute);
}

@Override
protected void onDialogClosed(boolean positiveResult){
    super.onDialogClosed(positiveResult);
    if(positiveResult){
        lastHour = firstPicker.getValue();
        lastMinute = secondPicker.getValue();

        if (lastHour ==0 && lastMinute == 0){
            lastMinute =1;
        }

        String time = String.valueOf(lastHour) + ":" + String.valueOf(lastMinute);

        if(callChangeListener(time)){
            persistString(time);
        }
    }
}

@Override
protected Object onGetDefaultValue(TypedArray a, int index){
    return a.getString(index);
}

@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue){
    String time = null;

    if(restoreValue){
        if (defaultValue == null){
            time = getPersistedString("00:00");
        } else {
            time = getPersistedString(defaultValue.toString());
        }
    } else {
        time = defaultValue.toString();
    }

    lastHour = tempHour =  getHour(time);
    lastMinute = tempMinute = getMinute(time);
}

private static class SavedState extends BaseSavedState {
    // Member that holds the setting's value
    // Change this data type to match the type saved by your Preference
    String value;

    public SavedState(Parcelable superState) {
        super(superState);
    }

    public SavedState(Parcel source) {
        super(source);
        // Get the current preference's value
        value = source.readString();  // Change this to read the appropriate data type
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        // Write the preference's value
        dest.writeString(value);  // Change this to write the appropriate data type
    }

    // Standard creator object using an instance of this class
    public static final Parcelable.Creator<SavedState> CREATOR =
            new Parcelable.Creator<SavedState>() {

        public SavedState createFromParcel(Parcel in) {
            return new SavedState(in);
        }

        public SavedState[] newArray(int size) {
            return new SavedState[size];
        }
    };
}

@Override
protected Parcelable onSaveInstanceState() {
    final Parcelable superState = super.onSaveInstanceState();
    // Check whether this Preference is persistent (continually saved)
    /*
    if (isPersistent()) {
        // No need to save instance state since it's persistent, use superclass state
        return superState;
    }
    */
    // Create instance of custom BaseSavedState
    final SavedState myState = new SavedState(superState);
    // Set the state's value with the class member that holds current setting value
    myState.value = String.valueOf(tempHour) + ":" + String.valueOf(tempMinute);
    return myState;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    // Check whether we saved the state in onSaveInstanceState
    if (state == null || !state.getClass().equals(SavedState.class)) {
        // Didn't save the state, so call superclass
        super.onRestoreInstanceState(state);
        return;
    }
    // Cast state to custom BaseSavedState and pass to superclass
    SavedState myState = (SavedState) state;
    super.onRestoreInstanceState(myState.getSuperState());
    // Set this Preference's widget to reflect the restored state
    rotatedHour = getHour(myState.value);
    rotatedMinute = getMinute(myState.value);
    firstPicker.setValue(rotatedHour);
    secondPicker.setValue(rotatedMinute);
}

}

There are two problems:

  1. The app crashes, when i open my custom preference, change values on one of the pickers, and then rotate the phone from portrait to landscape. The error is NuLLpointerException and it points to the line where i try to assing the restored value to one of my NumberPicker objects.
  2. This is more a question than a problem. I copied the BaseSavedState inner class and both onSaveInstanceState(), onRestoreInstanceState() functions from Android Developer homepage, but when i tried the app to restore values on orientation change, the phone showed the persisted values, not the latest values before orientation change. When i tried to examine the code with log messages, i discovered that my phone on SaveInstanceState isPersisted check exits the function and doesn`t operate with BaseSavedState object att all. I comented out that isPersisted check so now i can save and retrieve values from BaseSavedState object, but after that the problem nr. 1 appears. So my question is, what is the reasoning to skip the BaseSavedState object creation, if the preference is persistent. And is my decision to skip the persistent check and force the app to create BaseSavedState object a bad one?

Take a look at this implementation with 3 NumberPicker objects:

package com.bom.dom;

import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.NumberPicker;
import android.widget.NumberPicker.OnValueChangeListener;

public class TimePreference extends DialogPreference {

NumberPicker hoursNumberPicker;
NumberPicker minutesNumberPicker;
NumberPicker secondsNumberPicker;
int time;
int currentTime;

public TimePreference(Context context, AttributeSet attrs) {
    super(context, attrs);
}

@Override
protected void onBindDialogView(View view) {
    hoursNumberPicker = (NumberPicker) view.findViewById(R.id.numberpicker_hours);
    hoursNumberPicker.setMaxValue(24);
    hoursNumberPicker.setMinValue(0);
    hoursNumberPicker.setOnValueChangedListener(new OnValueChangeListener() {
        @Override
        public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
            updateCurrentTimeFromUI();
        }
    });
    minutesNumberPicker = (NumberPicker) view.findViewById(R.id.numberpicker_minutes);
    minutesNumberPicker.setMaxValue(59);
    minutesNumberPicker.setMinValue(0);
    minutesNumberPicker.setOnValueChangedListener(new OnValueChangeListener() {
        @Override
        public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
            updateCurrentTimeFromUI();
        }
    });
    secondsNumberPicker = (NumberPicker) view.findViewById(R.id.numberpicker_seconds);
    secondsNumberPicker.setMaxValue(59);
    secondsNumberPicker.setMinValue(0);
    secondsNumberPicker.setOnValueChangedListener(new OnValueChangeListener() {
        @Override
        public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
            updateCurrentTimeFromUI();
        }
    });
    updateUI();
    super.onBindDialogView(view);
}

@Override
protected void onDialogClosed(boolean positiveResult) {
    if (positiveResult) {
            time = currentTime;
            persistInt(time);
            return;
    }
    currentTime = time;
}

private void updateCurrentTimeFromUI() {
    int hours = hoursNumberPicker.getValue();
    int minutes = minutesNumberPicker.getValue();
    int seconds = secondsNumberPicker.getValue();
    currentTime = hours * 3600 + minutes * 60 + seconds;
}

@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
    if (restorePersistedValue) {
        time = getPersistedInt(1);
    } else {
        time = (Integer) defaultValue;
        persistInt(time);
    }
    currentTime = time;
}

@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
    Integer defaultValue = a.getInteger(index, 1);
    return defaultValue;
}

private void updateUI() {
    int hours = (int) (currentTime / 3600);
    int minutes = ((int) (currentTime / 60)) % 60;
    int seconds = currentTime % 60;
    hoursNumberPicker.setValue(hours);
    minutesNumberPicker.setValue(minutes);
    secondsNumberPicker.setValue(seconds);
}

@Override
protected Parcelable onSaveInstanceState() {
    final Parcelable superState = super.onSaveInstanceState();
    final SavedState myState = new SavedState(superState);
    myState.value = currentTime;
    return myState;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    if (state == null || !state.getClass().equals(SavedState.class)) {
        super.onRestoreInstanceState(state);
        return;
    }
    SavedState myState = (SavedState) state;
    currentTime = myState.value;
    super.onRestoreInstanceState(myState.getSuperState());
}

private static class SavedState extends BaseSavedState {
    int value;

    public SavedState(Parcelable superState) {
        super(superState);
    }

    public SavedState(Parcel source) {
        super(source);
        value = source.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        dest.writeInt(value);
    }

    @SuppressWarnings("unused")
    public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {

        public SavedState createFromParcel(Parcel in) {
            return new SavedState(in);
        }

        public SavedState[] newArray(int size) {
            return new SavedState[size];
        }
    };
}
}

The layout file is:

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

<NumberPicker
    android:id="@+id/numberpicker_hours"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

<NumberPicker
    android:id="@+id/numberpicker_minutes"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

<NumberPicker
    android:id="@+id/numberpicker_seconds"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

</LinearLayout>

I found the solution on first problem: To avoid the NullPointerException, i had to enclose the functions, that access NumberPicker objects, with a check, that determines if those pickers are initiated. I had this problem because on my app i have multiple my custom preference instances, and when i tried to follow the data path for saving/restoring functions, i discovered, that in logcat i had twice the amount of my own messages (not only for my onscreen preference, but also for the other prefrerence that uses my custom DialogPreference class). And because, i opened only one preference, the initialisation of NumberPicker objects in other preference didn`t happened, so accessing those pickers led (if i understood correctly) to NullPointerException. But i still would like to hear someone more experienced, that could explain the default behaviour with save/restore functions.

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