Rich Luick
Rich Luick

Reputation: 2364

Shared Preferences Null Pointer Exception

I am working on adding Shared Preferences to my Note taking Android App and I am running into a peculiar null pointer exception. I am currently testing settings for font size and typeface in my app and have been able to successfully save the shared preferences for both but am having trouble with the retrieval part. When the settings are changed, they successfully change the font and font size on a Fragment for the remainder of time the app is opened but I cannot restore them if the app is restarted.

First weird Null Pointer is in the onCreate.

OnCreate:

    //create a new note fragment if one has not been created yet
    mNoteFragment = (NoteFragment) getFragmentManager().findFragmentById(R.id.container);
    if (mNoteFragment == null) {
        mNoteFragment = new NoteFragment();
        getFragmentManager().beginTransaction()
                .replace(R.id.container, mNoteFragment).commit();
    }

    //restore SharedPreferences
    SharedPreferences sharedPrefs = getPreferences(0);
    int stylePref = sharedPrefs.getInt(SharedPreferanceConstants.PREF_FONT_SIZE, 2);
    String fontPref = sharedPrefs.getString(SharedPreferanceConstants.PREF_TYPEFACE, "");
    Log.e("STYLEID", String.valueOf(stylePref));
    Log.e("FONTTYPE", fontPref);
    onStyleChange(null , stylePref); //NULL POINTER EXCEPTION HERE
    onFontChange(null, fontPref);

The logs output "3" and "Impact" which are the correct values for size and font which indicates that stylePref and fontPref are not null.

The next Null Pointer is below.:

    @Override
    public void onStyleChange(CustomStyleDialogFragment dialog, int styleId) {
        Log.d("NOTEFRAGMENT", String.valueOf(mNoteFragment));
        mNoteFragment.setCustomStyle(styleId); //NULL POINTER EXCEPTION HERE
    }

    @Override
    public void onFontChange(CustomStyleDialogFragment dialog, String fontName) {
        mNoteFragment.setCustomFont(fontName);
    }

I have tested logging the value of styleId and got "3" so that doesn't seem to be the issue. mNoteFragment isnt null either based on the log.

Here is the third NP Exception in the NoteFragment.java. This is where I ultimately set the EditText view to the desired settings. I had no trouble changing font and size without shared preferences so I am not sure the issue here.

    public void setCustomFont(String fontName) {
        if(fontName.equals("Helvetica")) {
            mEditText.setTypeface(mHelvetica);
        }
        else if(fontName.equals("Helvetica-Neue")) {
            mEditText.setTypeface(mHelveticaNeue);
        }
        else if(fontName.equals("Impact")) {
            mEditText.setTypeface(mImpact);
        }
        else {
            mEditText.setTypeface(Typeface.DEFAULT);
        }
    }

    public void setCustomStyle(int styleId) {
        if(styleId == 1) {
            mEditText.setTextAppearance(getActivity(), android.R.style.TextAppearance_Small);
        }
        else if(styleId == 2) {
            mEditText.setTextAppearance(getActivity(), android.R.style.TextAppearance_Medium);
        }
        else if(styleId == 3) {
            //NULL POINTER EXCEPTION HERE
            mEditText.setTextAppearance(getActivity(), android.R.style.TextAppearance_Large);
        }
}

And of course, here is the logcat. Thanks in advance!

11-18 10:31:53.030  18966-18966/com.richluick.blocnotes I/Process﹕ Sending signal. PID: 18966 SIG: 9
11-18 10:35:32.838  19248-19248/com.richluick.blocnotes D/STYLEID﹕ 3
11-18 10:35:32.838  19248-19248/com.richluick.blocnotes D/FONTTYPE﹕ Impact
11-18 10:35:32.839  19248-19248/com.richluick.blocnotes D/ERROR﹕ NoteFragment{422972f8 id=0x7f090001}
11-18 10:35:32.840  19248-19248/com.richluick.blocnotes D/AndroidRuntime﹕ Shutting down VM
11-18 10:35:32.840  19248-19248/com.richluick.blocnotes W/dalvikvm﹕ threadid=1: thread exiting with uncaught exception (group=0x4197dd40)
11-18 10:35:32.842  19248-19248/com.richluick.blocnotes E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: com.richluick.blocnotes, PID: 19248
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.richluick.blocnotes/com.richluick.blocnotes.BlocNotes}: java.lang.NullPointerException
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2198)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2257)
            at android.app.ActivityThread.access$800(ActivityThread.java:139)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1210)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5097)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
            at dalvik.system.NativeStart.main(Native Method)
     Caused by: java.lang.NullPointerException
            at com.richluick.blocnotes.NoteFragment.setCustomStyle(NoteFragment.java:86)
            at com.richluick.blocnotes.BlocNotes.onStyleChange(BlocNotes.java:139)
            at com.richluick.blocnotes.BlocNotes.onCreate(BlocNotes.java:61)
            at android.app.Activity.performCreate(Activity.java:5248)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1110)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2162)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2257)
            at android.app.ActivityThread.access$800(ActivityThread.java:139)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1210)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5097)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
            at dalvik.system.NativeStart.main(Native Method)

NoteFragment.java

public class NoteFragment extends Fragment {

    public EditText mEditText;
    private Typeface mHelvetica;
    private Typeface mHelveticaNeue;
    private Typeface mImpact;

    private static final String TEXT = "text";

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
        savedInstanceState.putString(TEXT, mEditText.getText().toString());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_note, container, false);
        setHasOptionsMenu(true);

        //restore state of app when activity is destroyed and restarted
        mEditText = (EditText) rootView.findViewById(R.id.editText);
        if (savedInstanceState != null) {
            mEditText.setText(savedInstanceState.getString(TEXT));
        }

        //Store the font assets as variables
        mHelvetica = Typeface.createFromAsset(getActivity().getAssets(), "fonts/Helvetica_Reg.ttf");
        mHelveticaNeue = Typeface.createFromAsset(getActivity().getAssets(), "fonts/HelveticaNeue_Lt.ttf");
        mImpact = Typeface.createFromAsset(getActivity().getAssets(), "fonts/impact.ttf");

        return rootView;
    }

    /**
     * This is a setter method for setting the font the user has selected from the spinner
     *
     * param fontName the name of the font the user selected
     * @return void
     * */
    public void setCustomFont(String fontName) {
        if(fontName.equals("Helvetica")) {
            mEditText.setTypeface(mHelvetica);
        }
        else if(fontName.equals("Helvetica-Neue")) {
            mEditText.setTypeface(mHelveticaNeue);
        }
        else if(fontName.equals("Impact")) {
            mEditText.setTypeface(mImpact);
        }
        else {
            mEditText.setTypeface(Typeface.DEFAULT);
        }
    }

    /**
     * This is a setter method for setting the font style the user has selected from custom menu
     *
     * param styleId the integer id of the font stlye selected (SMALL, MEDIUM, LARGE)
     * @return void
     * */
    public void setCustomStyle(int styleId) {
        if(styleId == 1) {
            mEditText.setTextAppearance(getActivity(), android.R.style.TextAppearance_Small);
        }
        else if(styleId == 2) {
            mEditText.setTextAppearance(getActivity(), android.R.style.TextAppearance_Medium);
        }
        else if(styleId == 3) {
            Log.d("EDITTEXT", String.valueOf(mEditText));
            mEditText.setTextAppearance(getActivity(), android.R.style.TextAppearance_Large);
        }
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // Inflate the menu; this adds items to the action bar if it is present.
        super.onCreateOptionsMenu(menu, inflater);

        inflater = getActivity().getMenuInflater();
        inflater.inflate(R.menu.bloc_notes, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_erase) {
            mEditText.setText("");
        }
        return super.onOptionsItemSelected(item);
    }
}

Upvotes: 0

Views: 1589

Answers (4)

Rich Luick
Rich Luick

Reputation: 2364

Okay so I am not sure why this works but I was able to figure out the anwer based on this question:

onCreateView() in Fragment is not called immediately, even after FragmentManager.executePendingTransactions()

I added the following line

getFragmentManager().executePendingTransactions();

and then moved everthing to the onStart method

@Override
    protected void onStart() {
        super.onStart();

        //create a new note fragment if one has not been created yet
        mNoteFragment = (NoteFragment) getFragmentManager().findFragmentById(R.id.container);
        if (mNoteFragment == null) {
            mNoteFragment = new NoteFragment();
            getFragmentManager().beginTransaction().replace(R.id.container, mNoteFragment).commit();
            getFragmentManager().executePendingTransactions();
        }

        //restore SharedPreferences
        SharedPreferences sharedPrefs = getPreferences(0);
        int stylePref = sharedPrefs.getInt(SharedPreferanceConstants.PREF_FONT_SIZE, 2);
        String fontPref = sharedPrefs.getString(SharedPreferanceConstants.PREF_TYPEFACE, "");
        Log.d("STYLEID", String.valueOf(stylePref));
        Log.d("FONTTYPE", fontPref);
        onStyleChange(null , stylePref);
        onFontChange(null, fontPref);
    }

and for some reason it works fine now. If anyone could explain why that would be great

Upvotes: 0

Robin Eisenberg
Robin Eisenberg

Reputation: 1866

Your mEditText in your NoteFragment is null at this time, hence the NPE.

Just init your member mEditText in onCreate() or onActivityCreated() of the fragment, not onCreateView(). That should ensure that mEditText is not null when you call your setters.

Whatever happens, you should be protecting against a null mEditText in your setter functions.

EDIT this is what I meant:

public void setCustomFont(String fontName) {
      if(mEditText != null){
        if(fontName.equals("Helvetica")) {
            mEditText.setTypeface(mHelvetica);
        }
        else if(fontName.equals("Helvetica-Neue")) {
            mEditText.setTypeface(mHelveticaNeue);
        }
        else if(fontName.equals("Impact")) {
            mEditText.setTypeface(mImpact);
        }
        else {
            mEditText.setTypeface(Typeface.DEFAULT);
        }
        }
    }

EDIT 2: Oncreate will not be a good place. Basically you want to call your setter after the fragmentmanager has committed your change, so that mEditText has been initialized.

EDIT 3 - Try this with your initial code:

    getFragmentManager().beginTransaction().replace(R.id.container, mNoteFragment).commit();
    getFragmentManager().executePendingTransactions();

Upvotes: 1

Flo
Flo

Reputation: 1499

In your SomeActivity.onCreate() you are creating an instance of NoteFragment, but even if you commit it via a transaction, it won't be inflated and created right away. Which basically means, the NoteFragment.onCreateView() was not executed and the fragments UI is not yet created. However, your Activity.onCreate() assumes, that mFragment.mEditText is already set, which is not the case, so you run into a NullPointerException.

Take a look at the FragmentTransation.commit method: http://developer.android.com/reference/android/support/v4/app/FragmentTransaction.html#commit()

Schedules a commit of this transaction. The commit does not happen immediately; it will 
be scheduled as work on the main thread to be done the next time that thread is ready.

Upvotes: 1

Thiago Souto
Thiago Souto

Reputation: 771

Try use 'apply' instead of 'commit' , the justification is here -> What's the difference between commit() and apply() in Shared Preference by Lukas Knuth.

But basically, apply() will commits its changes to the in-memory SharedPreferences immediately.

It's strong recommended use a class to manager your preferences, like 'MyPreferencesManager'.

Upvotes: -1

Related Questions