0

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);
    }
}
Rich Luick
  • 2,354
  • 22
  • 35
  • When you saved your preferences, did you commit them ? – Nanis Nov 18 '14 at 15:40
  • Yes, I did commit them – Rich Luick Nov 18 '14 at 15:42
  • `com.richluick.blocnotes.NoteFragment.setCustomStyle(NoteFragment.java:86)` which prolly means that `mEditText` ... – Selvin Nov 18 '14 at 15:46
  • @Selvin I thought that may be the issue, however I had no issue changing the font size and type using mEditText before I introduced Shared Preferences and had no null pointer issues. How would introducing Shared Preferences make mEditText suddenly null? – Rich Luick Nov 18 '14 at 15:48
  • It wouldn't, but you can't be 100% sure unless you are unit testing everything. Can you post the NoteFragment class? – Robin Eisenberg Nov 18 '14 at 15:50
  • @RobinEisenberg just added a Log to see the value of mEditText and it returned null. Its very confusing to me why that would happen but I am more concerned with how to fix it. I will post the NoteFragment above – Rich Luick Nov 18 '14 at 15:51

4 Answers4

1

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();
Robin Eisenberg
  • 1,836
  • 18
  • 26
  • I'm not sure I agree. OnCreate might be dangerous but onActivityCreated is the right place isn't it? OnCreateView has not been called yet at this point I believe. Can you be more elaborate in your answer? It just seems like you went around downvoting without giving a good reason... – Robin Eisenberg Nov 18 '14 at 16:10
  • I still get the null pointer in onCreate. What would be an example of protecting it in a setter function? – Rich Luick Nov 18 '14 at 16:13
  • if(mEditText!=null){use mEditText } ... but it will only mask the problem onActivityCreated is good place too that's why i did not use it in my comments – Selvin Nov 18 '14 at 16:15
  • @Selvin Still getting the NPE using onActivityCreated. I agree that your solution above would mask the problem and avoid the error, but it would not solve the issue of mEditText being null. – Rich Luick Nov 18 '14 at 16:20
  • No I was not trying to tell you how to mask the problem. I was just saying that it is good practice to test against null pointers wherever you code. Nothing to do with fixing this issue. – Robin Eisenberg Nov 18 '14 at 16:21
  • Have you tried forcing the commit with fragmentManager.executePendingTransactions() ? – Robin Eisenberg Nov 18 '14 at 16:22
  • Okay, so I am not super familiar with the FragmentManager yet. Where would I place that in my code and what does it do? – Rich Luick Nov 18 '14 at 16:29
  • Basically the operation that you request (replacing a fragment) is done asynchronously. So the operation is not done yet when you call setCustomFont, and your mEditText is null. If you force the change (commit) now, then onCreateView is called and your mEditText is initialized. See my EDIT 3. – Robin Eisenberg Nov 18 '14 at 16:31
  • Okay so I added in the if statement when I am creating my NoteFragment is that the correct place? I then ran the program and am now getting a single NPE on if (mNoteFragment == null) – Rich Luick Nov 18 '14 at 16:35
  • "NPE on: `if (mNoteFragment == null)`" <= not possible at all – Selvin Nov 18 '14 at 17:56
  • @Selvin that went away the next time I ran it. Must have been a weird thing. But anyways I think I solved it. Take a look at my answer above. I added the executePendingTransactions() as you said but that didnt work. I found another thread that suggested moving it all to onStart and it worked fine! – Rich Luick Nov 18 '14 at 17:59
1

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.
Flo
  • 1,469
  • 1
  • 18
  • 27
  • interesting ... so the solution could be: `if(mEditText is null) {deferred the setting into onCreateView} else {do settings now}` – Selvin Nov 18 '14 at 16:26
  • Okay, I had kinda figured that onCreateView was never being executed so thanks for clarifying that. I do not really understand why though. I am a bit confused with what you mean by "commit it via a transaction" and the FragmentTransation.commit method in general. Could you maybe give me an example of this in action? I am pretty lost otherwise – Rich Luick Nov 18 '14 at 16:27
  • 1
    What this means is that basically you ask the fragment manager to replace a fragment, and the fragment manager will do it asynchronously. That means that it will 'remember it has to replace your fragment' and will do it when the CPU can handle it (isn't under too much load). What it means is that whatever you call after the commit() line doesn't ensure that the operation started by the commit() is done. – Robin Eisenberg Nov 18 '14 at 16:38
  • In order to resolve this, i would put the method `onStyleChange` and all related methods that directly interact with the fragments view into the `NoteFragment` itself, e.g. you could put it into `NoteFragment.onCreateView()` The fragment can create its own instance of `SharedPreferences` and read the same underlying data. – Flo Nov 22 '14 at 03:23
0

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

Community
  • 1
  • 1
Rich Luick
  • 2,354
  • 22
  • 35
  • what about only changes in the NoteFragment : https://gist.github.com/SelvinPL/e9549885632669c7a37f (without executePendingTransactions() ) – Selvin Nov 18 '14 at 18:05
  • Im not sure I completely follow? The default style is set in the MainActivity and any changes are actually handled in a separate settings fragment – Rich Luick Nov 18 '14 at 18:12
  • no ... all code is from the NoteFragment ... if mEditText is not null it works in usual way ... if onCreateView wasn't called mEditText == null so setCustomFont is called again on onCreateView (fully commited fragment) and SOMEDEFAULTSTYLE = 1 or 2 or 3 – Selvin Nov 18 '14 at 18:15
-1

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'.

Community
  • 1
  • 1
Thiago Souto
  • 761
  • 5
  • 13
  • this should be a comment as there is no connection without your answer and NPE in the question ... – Selvin Nov 18 '14 at 16:02
  • I have heard that creating a class is the way to go so I will use that going down the road. This is my first foray into SharedPreferences so I am trying to get it to work like this first. Thanks for the tip! – Rich Luick Nov 18 '14 at 16:08
  • it is connected with a SP not Fragment manager ... take a look at Flo answer .. maybe you thoght about FM not SP ... – Selvin Nov 18 '14 at 16:16