Bioto
Bioto

Reputation: 1117

Android onSaveInstanceState not getting class variables

i'm trying to save my ListView data in onSaveInstanceState. For some reason when I call a class variable inside onSaveInstanceState its not populated with the correct data.

public class ArtistTopTen extends Fragment {

    private ListView artistTopTen;

    private String artistId;

    private Tracks listTracks;

    private exposed.coding.android_nano.Adapters.ArtistTopTen topTenListAdapter;

    private SpotifyApi spotifyApi;
    private SpotifyService spotifyService;

    private MusicPlayerDialogFragment musicPlayerDialogFragment;


    public ArtistTopTen() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        final View view = inflater.inflate(R.layout.fragment_artist_top_ten, container, false);

        if(getActivity().findViewById(R.id.topten_container) == null) {
            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }

        // Start the service
        getActivity().startService(new Intent(getActivity(), Music.class));

        if(savedInstanceState == null) {
            try {
                if (getActivity().getIntent().hasExtra("id")) {
                    artistId = getActivity().getIntent().getStringExtra("id");
                } else {
                    artistId = getArguments().getString("artistid");
                }
            } catch (Exception e) {
                Log.e("ERROR", e.toString());
            }
        }else {
            Log.e("NOTICE", "SavedInstanceState has data");
        }

        if(artistId != null) {

            artistTopTen = (ListView) view.findViewById(R.id.artist_top_ten);
            topTenListAdapter = new exposed.coding.android_nano.Adapters.ArtistTopTen(getActivity(), listTracks);

            spotifyApi = new SpotifyApi();
            spotifyService = spotifyApi.getService();

            SharedPreferences sharedPreferences = getActivity().getPreferences(Context.MODE_PRIVATE);

            HashMap country = new HashMap<String, Object>();

            if (sharedPreferences.getString("country", "us").length() != 0) {
                country.put("country", sharedPreferences.getString("country", "us").toString());
            } else {
                country.put("country", "us");
            }

            spotifyService.getArtistTopTrack(artistId, country, new Callback<Tracks>() {
                @Override
                public void success(Tracks tracks, Response response) {
                    listTracks = tracks;
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            notifyAdapter();
                        }
                    });
                }

                @Override
                public void failure(RetrofitError error) {
                    Toast.makeText(view.getContext(), "Somthing went wrong", Toast.LENGTH_LONG).show();
                }
            });

            artistTopTen.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Intent intent = new Intent(":MediaPlayerUpdateList");
                intent.putExtra("tracks", new ParcelablesTracks(listTracks, position));
                getActivity().sendBroadcast(intent);

                if(getActivity().findViewById(R.id.topten_container) != null) {
                    musicPlayerDialogFragment = new MusicPlayerDialogFragment();
                    musicPlayerDialogFragment.setCancelable(false);
                    musicPlayerDialogFragment.show(getFragmentManager(), "fragment_music_player");
                }else {
                    intent = new Intent(getActivity(), MusicPlayerActivity.class);
                    intent.putExtra("returning", false);
                    startActivity(intent);
                }
                }
            });
        }
    if(savedInstanceState != null) {

    }

    return view;
}

private void notifyAdapter() {
    Log.e("First Track Name - SHOWING", listTracks.tracks.get(0).name);
    topTenListAdapter.setListTracks(listTracks);

    if(artistTopTen.getAdapter() == null) {
        artistTopTen.setAdapter(topTenListAdapter);
    }else {
        topTenListAdapter.notifyDataSetChanged();
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    Log.e("First Track Name - NOT SHOWING", listTracks.tracks.get(0).name);
    //outState.putParcelable("list", new ParcelablesTracks(listTracks, 0));
    super.onSaveInstanceState(outState);
}
}

If you look in the function notifiyAdapter() you can see that i'm printing out the name of the song:

Log.e("First Track Name - SHOWING", listTracks.tracks.get(0).name);

However when onSaveInstanceState is called it errors out whenever I try to access tracks:

Log.e("First Track Name - NOT SHOWING", listTracks.tracks.get(0).name);

The error:

07-16 10:12:07.759  29091-29091/exposed.coding.android_nano E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: exposed.coding.android_nano, PID: 29091
    java.lang.NullPointerException: Attempt to read from field 'java.util.List kaaes.spotify.webapi.android.models.Tracks.tracks' on a null object reference
            at exposed.coding.android_nano.Fragments.ArtistTopTen.onSaveInstanceState(ArtistTopTen.java:162)
            at android.app.Fragment.performSaveInstanceState(Fragment.java:2198)
            at android.app.FragmentManagerImpl.saveFragmentBasicState(FragmentManager.java:1605)
            at android.app.FragmentManagerImpl.saveAllState(FragmentManager.java:1662)
            at android.app.Activity.onSaveInstanceState(Activity.java:1367)
            at android.app.Activity.performSaveInstanceState(Activity.java:1298)
            at android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.java:1288)
            at android.app.ActivityThread.callCallActivityOnSaveInstanceState(ActivityThread.java:3961)
            at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3924)
            at android.app.ActivityThread.access$900(ActivityThread.java:151)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1309)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5257)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

I'm pretty new to Android development but i'm using the same process to save data in another fragment and its working just fine. I'm not sure whats going on here.

Repo: https://github.com/Bioto/Android-Nano-Degree-Project1.2

EDIT:

Setting listTracks:

E/Setting listTracks﹕ ArtistTopTen{98c918d #2 id=0x7f0c0051}

onSaveInstanceState:

E/onSaveInstanceState()﹕ ArtistTopTen{3b7de284 #1 id=0x7f0c0051}

Upvotes: 0

Views: 98

Answers (1)

Kevin Coppock
Kevin Coppock

Reputation: 134664

EDIT: So what seems to be happening is that the Fragment instance that's receiving the response is not the same one that is saving its state. This is probably due to how you're handling the callback for the Spotify request.

I think the general idea of what's happening is:

  1. Fragment 98c918d is created
  2. A request is sent to request tracks, using an anonymous implementation of Callback that holds a reference to fragment 98c918d.
  3. Configuration change occurs (screen rotation), and fragment 98c918d is destroyed.
  4. New instance of fragment (3b7de284) is created.
  5. The request finishes, and fragment 98c918d receives the results.
  6. Another config change happens, and 3b7de284 tries to save its state, but doesn't actually have the data.

So what you're actually seeing is a memory leak (where your old Fragment is held until the request completes). I would suggest looking into the Loader framework to handle network requests across configuration changes. Alternatively, put that work into an IntentService, persist the tracks once they're loaded, and broadcast the event. If the Fragment is still around and receives the broadcast, it can load the new results. Otherwise, when it next comes to onStart() or onResume() it can look for any existing results (that may have been loaded while the fragment was not listening).


Attempt to read from field 'java.util.List kaaes.spotify.webapi.android.models.Tracks.tracks' on a null object reference

listTracks is null. So calling listTracks.tracks will cause a NullPointerException. There seem to be cases where listTracks is never assigned (it is only assigned in your success() method for getArtistTopTracks(), so if that fails for any reason, it will never be assigned. In this case, it's probably sufficient to wrap that line in a null-check; i.e.:

if (listTracks != null) {
    // do stuff with listTracks
}

Upvotes: 2

Related Questions