Search code examples
androidandroid-layoutandroid-timepicker

How to resolve an inflate exception in a custom timepicker?


I have create a custom time picker dialog in my android app, and android studio keeps showing me this warning on the code that "inflater.inflate may produce null exception".

Here are the xml files for the custom timepicker:

attrs.xml

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

<resources>
    <attr name="timePickerStyle" format="reference" />
    <declare-styleable name="CustomTimePicker">
       <attr name="internalLayout" format="reference"  />
    </declare-styleable>
 </resources>

time_picker_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<app.customview.CustomTimePicker xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/timePicker"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

time_picker.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_gravity="center_horizontal"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layoutDirection="ltr">
        <!-- hour -->
        <NumberPicker
            android:id="@+id/hour"
            android:layout_width="70dip"
            android:layout_height="wrap_content"
            android:focusable="true"
            android:focusableInTouchMode="true"
            />

        <!-- minute -->
        <NumberPicker
            android:id="@+id/minute"
            android:layout_width="70dip"
            android:layout_height="wrap_content"
            android:layout_marginStart="5dip"
            android:focusable="true"
            android:focusableInTouchMode="true"
            />

        <!-- minute -->
        <NumberPicker
            android:id="@+id/amPm"
            android:layout_width="70dip"
            android:layout_height="wrap_content"
            android:layout_marginStart="5dip"
            android:focusable="true"
            android:focusableInTouchMode="true"
            />

</LinearLayout>

CustomTimePickerDialog.Java

package app.customview;


import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TimePicker;

import app.customview.CustomTimePicker.OnTimeChangedListener;

/**
 * A dialog that prompts the user for the time of day using a {@link TimePicker}.
 * <p/>
 * <p>See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
 * guide.</p>
 */
public class CustomTimePickerDialog extends AlertDialog
        implements OnClickListener, OnTimeChangedListener {

    private static final String HOUR = "hour";
    private static final String MINUTE = "minute";
    private static final String IS_24_HOUR = "is24hour";
    private final CustomTimePicker mTimePicker;
    private final OnTimeSetListener mCallback;
    int mInitialHourOfDay;
    int mInitialMinute;
    boolean mIs24HourView;
    private View view;
    /**
     * @param context      Parent.
     * @param callBack     How parent is notified.
     * @param hourOfDay    The initial hour.
     * @param minute       The initial minute.
     * @param is24HourView Whether this is a 24 hour view, or AM/PM.
     */
    public CustomTimePickerDialog(Context context,
                                  OnTimeSetListener callBack,
                                  int hourOfDay, int minute, boolean is24HourView) {
        this(context, 0, callBack, hourOfDay, minute, is24HourView);
    }

    /**
     * @param context      Parent.
     * @param theme        the theme to apply to this dialog
     * @param callBack     How parent is notified.
     * @param hourOfDay    The initial hour.
     * @param minute       The initial minute.
     * @param is24HourView Whether this is a 24 hour view, or AM/PM.
     */
    public CustomTimePickerDialog(Context context,
                                  int theme,
                                  OnTimeSetListener callBack,
                                  int hourOfDay, int minute, boolean is24HourView) {
        super(context, theme);
        mCallback = callBack;
        mInitialHourOfDay = hourOfDay;
        mInitialMinute = minute;
        mIs24HourView = is24HourView;

        setIcon(0);
        // buggy
        setTitle("Set Time");

        //Context themeContext = getContext();
        setButton(BUTTON_POSITIVE, "Done", this);

        LayoutInflater inflater = getLayoutInflater();

        try {
            view = inflater.inflate(R.layout.time_picker_dialog, null);
        } catch(android.view.InflateException e) {
            view = inflater.inflate(R.layout.time_picker_dialog, null);
            // do nothing
        }
        setView(view);
        mTimePicker = (CustomTimePicker) view.findViewById(R.id.timePicker);

        // initialize state
        mTimePicker.setIs24HourView(mIs24HourView);
        mTimePicker.setCurrentHour(mInitialHourOfDay);
        mTimePicker.setCurrentMinute(mInitialMinute);
        mTimePicker.setOnTimeChangedListener(this);

    }

    public void onClick(DialogInterface dialog, int which) {
        tryNotifyTimeSet();
    }

    public void updateTime(int hourOfDay, int minutOfHour) {
        mTimePicker.setCurrentHour(hourOfDay);
        mTimePicker.setCurrentMinute(minutOfHour);
    }

    private void tryNotifyTimeSet() {
        if (mCallback != null) {
            mTimePicker.clearFocus();
            mCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
                    mTimePicker.getCurrentMinute());
        }
    }

    @Override
    protected void onStop() {
        tryNotifyTimeSet();
        super.onStop();
    }

    @Override
    public Bundle onSaveInstanceState() {
        Bundle state = super.onSaveInstanceState();
        state.putInt(HOUR, mTimePicker.getCurrentHour());
        state.putInt(MINUTE, mTimePicker.getCurrentMinute());
        state.putBoolean(IS_24_HOUR, mTimePicker.is24HourView());
        return state;
    }

    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        int hour = savedInstanceState.getInt(HOUR);
        int minute = savedInstanceState.getInt(MINUTE);
        mTimePicker.setIs24HourView(savedInstanceState.getBoolean(IS_24_HOUR));
        mTimePicker.setCurrentHour(hour);
        mTimePicker.setCurrentMinute(minute);
    }

    @Override
    public void onTimeChanged(CustomTimePicker view, int hourOfDay, int minute) {
        // TODO Auto-generated method stub

    }


    /**
     * The callback interface used to indicate the user is done filling in
     * the time (they clicked on the 'Set' button).
     */
    public interface OnTimeSetListener {

        /**
         * @param view      The view associated with this listener.
         * @param hourOfDay The hour that was set.
         * @param minute    The minute that was set.
         */
        void onTimeSet(CustomTimePicker view, int hourOfDay, int minute);
    }

}

CustomTimePicker.java

public CustomTimePicker(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        // initialization based on locale
        setCurrentLocale(Locale.getDefault());

        // process style attributes
         TypedArray attributesArray = context.obtainStyledAttributes(
                attrs, R.styleable.CustomTimePicker, defStyle, 0);
        int layoutResourceId = attributesArray.getResourceId(
           R.styleable.CustomTimePicker_internalLayout, R.layout.time_picker);
         attributesArray.recycle();

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(layoutResourceId, this, true);

        // hour
        mHourSpinner = (NumberPicker) findViewById(R.id.hour);
        mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
            public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
                if (!is24HourView()) {
                    if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY)
                            || (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) {
                        mIsAm = !mIsAm;
                        updateAmPmControl();
                    }
                }
                onTimeChanged();
            }
        });


        // minute
        mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
        mMinuteSpinner.setMinValue(0);
        mMinuteSpinner.setMaxValue(3);
        mMinuteSpinner.setDisplayedValues(new String[]{"0", "15", "30", "45"});
        mMinuteSpinner.setFormatter(new TwoDigitFormatter());
        mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
            public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
                onTimeChanged();
            }
        });

        /* Get the localized am/pm strings and use them in the spinner */
        mAmPmStrings = new DateFormatSymbols().getAmPmStrings();

        // am/pm
        View amPmView = findViewById(R.id.amPm);
        if (amPmView instanceof Button) {
            mAmPmSpinner = null;
            mAmPmButton = (Button) amPmView;
            mAmPmButton.setOnClickListener(new OnClickListener() {
                public void onClick(View button) {
                    button.requestFocus();
                    mIsAm = !mIsAm;
                    updateAmPmControl();
                    onTimeChanged();
                }
            });
        } else {
            mAmPmButton = null;
            mAmPmSpinner = (NumberPicker) amPmView;
            mAmPmSpinner.setMinValue(0);
            mAmPmSpinner.setMaxValue(1);
            mAmPmSpinner.setDisplayedValues(mAmPmStrings);
            mAmPmSpinner.setOnValueChangedListener(new OnValueChangeListener() {
                public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
                    picker.requestFocus();
                    mIsAm = !mIsAm;
                    updateAmPmControl();
                    onTimeChanged();
                }
            });
        }

        // update controls to initial state
        updateHourControl();
        updateAmPmControl();

        setOnTimeChangedListener(NO_OP_CHANGE_LISTENER);

        // set to current time
        setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
        setCurrentMinute(mTempCalendar.get(Calendar.MINUTE));

        if (!isEnabled()) {
            setEnabled(false);
        }

    }

=============================

It is this line in CustomTimePickerDialog.Java which android studio keeps saying "may producc java.lang.null exception..." and the app also crashes if I try to access the view object in anyway, as it is returning null. Strangely, occasionally this piece of code runs fine and i can see the timepicker and app works fine, but 90% of time, the view inflate code returns null and app crashes.

Can somebody point out what it is I can do to fix this (fix the warning and eventually make sure the view inflation works).

Thanks

NOTE TO MODERATORS: Please do not mark this as duplicate, this is not a generic "null exception" error problem. I have searched for days before asking this question as I cannot find any answer that might solve my problem.


Solution

  • You are probably getting that error because you are getting LayoutInflator using System services, which are not available to Activities before onCreate(). Try getting layout inflator in the following way:

    LayoutInflater inflater = getLayoutInflater();