javaandroidandroid-fragmentssavestate

Why does saveStateInstance not work on screen orientation?


I am making a tour guide app that is made up of one main activity with and two fragments. The top fragment contains a list of cities and when you clock on a city the bottom fragment displays a description of that city. The problem is that when I change orientation I get an error saying "Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference"

Here is the city list fragment:

        package com.example.tourguide;


        import android.os.Bundle;
        import android.support.annotation.NonNull;
        import android.support.v4.app.Fragment;
        import android.view.LayoutInflater;
        import android.view.View;
        import android.view.ViewGroup;
        import android.widget.AdapterView;
        import android.widget.ArrayAdapter;
        import android.widget.ListView;


        /**
         * A simple {@link Fragment} subclass.
         */
        public class CityFragment extends Fragment {

            View view;
            String[] cities;
            String[] descriptions;
            ListView listView;
            DescriptionFragment text;
            int mPosition;

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


            @Override
            public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                     Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                // Check whether we're recreating a previously destroyed instance
                if (savedInstanceState != null) {
                    // Restore value of member from saved state
                    text = (DescriptionFragment) getFragmentManager().getFragment(savedInstanceState, getString(R.string.DESCRIPTION_FRAG));
                    mPosition = savedInstanceState.getInt(getString(R.string.POSITION));
                    cities = savedInstanceState.getStringArray(getString(R.string.CITY_ARRAY));
                    descriptions = savedInstanceState.getStringArray(getString(R.string.DESCRIPTION_ARRAY));
                    text.change(descriptions[mPosition], cities[mPosition]);
                }

                    // Get views
                    this.view = inflater.inflate(R.layout.fragment_city, container, false);
                    cities = getResources().getStringArray(R.array.cities);
                    descriptions = getResources().getStringArray(R.array.cities_description);
                    listView = this.view.findViewById(R.id.city_list);

                    // Create an ArrayAdapter
                    ArrayAdapter<String> listViewAdapter = new ArrayAdapter<String>(getActivity(),
                            android.R.layout.simple_list_item_1, cities);

                    // Set adapter on the listView
                    listView.setAdapter(listViewAdapter);

                // Set an item click listener for ListView
                listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

                    @Override
                    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                        // Get the selected item text from ListView
                        text = (DescriptionFragment) getFragmentManager().findFragmentById(R.id.fragmentBottom);
                        text.change(descriptions[Integer.parseInt(String.valueOf(position))], cities[Integer.parseInt(String.valueOf(position))]);
                        mPosition = position;
                    }
                });

                // Inflate the layout for this fragment
                return this.view;

            }

            @Override
            public void onSaveInstanceState(@NonNull Bundle outState) {
                super.onSaveInstanceState(outState);

                // Save the fragments Instance
                getFragmentManager().putFragment(outState, getString(R.string.DESCRIPTION_FRAG), text);
                outState.putInt(getString(R.string.POSITION), mPosition);
                outState.putStringArray(getString(R.string.CITY_ARRAY), cities);
                outState.putStringArray(getString(R.string.DESCRIPTION_ARRAY), descriptions);
            }
        }

And here is the description fragment:

        package com.example.tourguide;


        import android.os.Bundle;
        import android.support.annotation.NonNull;
        import android.support.v4.app.Fragment;
        import android.view.LayoutInflater;
        import android.view.View;
        import android.view.ViewGroup;
        import android.widget.TextView;


        /**
         * A simple {@link Fragment} subclass.
         */
        public class DescriptionFragment extends Fragment {

            TextView cityName;
            TextView text;

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


            @Override
            public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                     Bundle savedInstanceState) {

                if (savedInstanceState != null) {

                }

                // Inflate the layout for this fragment
                View view = inflater.inflate(R.layout.fragment_description, container, false);
                text = view.findViewById(R.id.city_description);
                cityName = view.findViewById(R.id.city_name);
                return view;
            }

            public void change(String description, String city) {
                text.setText(description);
                cityName.setText(city);
            }
        }

The text.change method seems to be where the problem lies but how else can I implement it to save the description state?

            if (savedInstanceState != null) {
                // Restore value of member from saved state
                text = (DescriptionFragment) getFragmentManager().getFragment(savedInstanceState, getString(R.string.DESCRIPTION_FRAG));
                mPosition = savedInstanceState.getInt(getString(R.string.POSITION));
                cities = savedInstanceState.getStringArray(getString(R.string.CITY_ARRAY));
                descriptions = savedInstanceState.getStringArray(getString(R.string.DESCRIPTION_ARRAY));
                text.change(descriptions[mPosition], cities[mPosition]);
            }

Solution

  • Why don't you just store the state within the DescriptionFragment.

    /**
     * A simple {@link Fragment} subclass.
     */
    public class DescriptionFragment extends Fragment {
    
        private static final String DESCRIPTION = "DESCRIPTION";
        private static final String CITY = "CITY";
    
        TextView cityName;
        TextView text;
    
        public DescriptionFragment() {
            // Required empty public constructor
        }
    
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_description, container, false);
        }
    
        @Override
        public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            text = view.findViewById(R.id.city_description);
            cityName = view.findViewById(R.id.city_name);
    
            if (savedInstanceState != null) {
                String description = savedInstanceState.getString(DESCRIPTION);
                String city = savedInstanceState.getString(CITY);
                change(description, city);
            }
        }
    
        @Override
        public void onSaveInstanceState(@NonNull Bundle outState) {
            super.onSaveInstanceState(outState);
    
            // Save the fragments Instance
            outState.putString(DESCRIPTION, text.getText().toString());
            outState.putString(CITY, cityName.getText().toString());
        }
    
        public void change(String description, String city) {
            text.setText(description);
            cityName.setText(city);
        }
    }