简体   繁体   中英

SharedPreferences are empty when called for the first time in Fragment

I have a sample android application where depending on the location (zip code) and temperature units set in settings (SharedPreference), the application displays 7 days weather.

It seems like when the application fetches temperature for the first time and checks what is the temperature unit set in SharedPreference, it comes as empty and isMetric is set to TRUE. Utility.isMetric can be improved to signify that there was no data fetched from SharedPreference but my question is why the SharedPreference is empty when called for first time in Utility.isMetric from ForecastFragment's onCreateView?

Utility.isMetric accessing SharedPreference

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);

ForecastFragment calling Utility.isMetric

boolean isMetric = Utility.isMetric(getActivity());

I have logcat that shows this behavior, let me know if you would like to see that.

As there is a maximum limit on no. of characters, complete code can be accessed at https://github.com/gosaliajigar/CSC519/tree/master/CSC519-HW6

ForecastFragment

public class ForecastFragment extends Fragment implements LoaderCallbacks<Cursor> {

    private SimpleCursorAdapter mForecastAdapter;

    private static final int FORECAST_LOADER = 0;
    private String mLocation;

    // For the forecast view we're showing only a small subset of the stored data.
    // Specify the columns we need.
    private static final String[] FORECAST_COLUMNS = {
            // In this case the id needs to be fully qualified with a table name, since
            // the content provider joins the location & weather tables in the background
            // (both have an _id column)
            // On the one hand, that's annoying.  On the other, you can search the weather table
            // using the location set by the user, which is only in the Location table.
            // So the convenience is worth it.
            WeatherEntry.TABLE_NAME + "." + WeatherEntry._ID,
            WeatherEntry.COLUMN_DATETEXT,
            WeatherEntry.COLUMN_SHORT_DESC,
            WeatherEntry.COLUMN_MAX_TEMP,
            WeatherEntry.COLUMN_MIN_TEMP,
            LocationEntry.COLUMN_LOCATION_SETTING
    };


    // These indices are tied to FORECAST_COLUMNS.  If FORECAST_COLUMNS changes, these
    // must change.
    public static final int COL_WEATHER_ID = 0;
    public static final int COL_WEATHER_DATE = 1;
    public static final int COL_WEATHER_DESC = 2;
    public static final int COL_WEATHER_MAX_TEMP = 3;
    public static final int COL_WEATHER_MIN_TEMP = 4;
    public static final int COL_LOCATION_SETTING = 5;

    public ForecastFragment() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Add this line in order for this fragment to handle menu events.
        setHasOptionsMenu(true);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.forecastfragment, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_refresh) {
            updateWeather();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        final String[] columns = {WeatherEntry.COLUMN_DATETEXT,
                WeatherEntry.COLUMN_SHORT_DESC,
                WeatherEntry.COLUMN_MAX_TEMP,
                WeatherEntry.COLUMN_MIN_TEMP
        };

        final int[] viewIDs = {R.id.list_item_date_textview,
                R.id.list_item_forecast_textview,
                R.id.list_item_high_textview,
                R.id.list_item_low_textview
        };

        // The SimpleCursorAdapter will take data from the database through the
        // Loader and use it to populate the ListView it's attached to.
        mForecastAdapter = new SimpleCursorAdapter(
                getActivity(),
                R.layout.list_item_forecast,
                null,
                columns,
                viewIDs,
                0);
        mForecastAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
            @Override
            public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
                boolean isMetric = Utility.isMetric(getActivity());
                switch (columnIndex) {
                    case COL_WEATHER_MAX_TEMP: {
                        String high = Utility.formatTemperature(
                                cursor.getDouble(cursor.getColumnIndex(WeatherEntry.COLUMN_MAX_TEMP)), isMetric);
                        ((TextView) view).setText(high);
                        return true;
                    }
                    case COL_WEATHER_MIN_TEMP: {
                        // we have to do some formatting and possibly a conversion
                        String low = Utility.formatTemperature(
                                cursor.getDouble(cursor.getColumnIndex(WeatherEntry.COLUMN_MIN_TEMP)), isMetric);
                        ((TextView) view).setText(low);
                        return true;
                    }
                    case COL_WEATHER_DATE: {
                        String dateString = Utility.formatDate(
                                cursor.getString(cursor.getColumnIndex(WeatherEntry.COLUMN_DATETEXT)));
                        ((TextView) view).setText(dateString);
                        return true;
                    }
                }
                return false;
            }
        });

        View rootView = inflater.inflate(R.layout.fragment_main, container, false);

        // Get a reference to the ListView, and attach this adapter to it.
        ListView listView = (ListView) rootView.findViewById(R.id.listview_forecast);
        listView.setAdapter(mForecastAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                Cursor cursor = mForecastAdapter.getCursor();
                if (cursor != null && cursor.moveToPosition(position)) {
                    Intent intent = new Intent(getActivity(), DetailActivity.class)
                            .putExtra(DetailActivity.DATE_KEY, cursor.getString(COL_WEATHER_DATE));
                    startActivity(intent);
                }
            }
        });

        return rootView;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        getLoaderManager().initLoader(FORECAST_LOADER, null, this);
        super.onActivityCreated(savedInstanceState);
    }

    private void updateWeather() {
        String location = Utility.getPreferredLocation(getActivity());
        // update weather only if location is not an EMPTY string
        if (location != null
                && location.length() > 0) {
            new FetchWeatherTask(getActivity()).execute(location, SettingsActivity.FORECAST_DAYS);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (mLocation != null && !mLocation.equals(Utility.getPreferredLocation(getActivity()))) {
            getLoaderManager().restartLoader(FORECAST_LOADER, null, this);
        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created.  This
        // fragment only uses one loader, so we don't care about checking the id.

        // To only show current and future dates, get the String representation for today,
        // and filter the query to return weather only for dates after or including today.
        // Only return data after today.
        String startDate = WeatherContract.getDbDateString(new Date());

        // Sort order:  Ascending, by date.
        String sortOrder = WeatherEntry.COLUMN_DATETEXT + " ASC";

        mLocation = Utility.getPreferredLocation(getActivity());

        if (mLocation == null || mLocation.length() == 0) {
            mLocation = "00000";
        }

        Uri weatherForLocationUri = WeatherContract.WeatherEntry.buildWeatherLocationWithStartDate(mLocation, startDate);
        Log.d(getString(R.string.app_name), weatherForLocationUri.toString());

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        return new CursorLoader(
                getActivity(),
                weatherForLocationUri,
                FORECAST_COLUMNS,
                null,
                null,
                sortOrder
        );
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        mForecastAdapter.swapCursor(data);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        mForecastAdapter.swapCursor(null);
    }
}

Utility

public class Utility {
    public static String getPreferredLocation(Context context) {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        return prefs.getString(context.getString(R.string.pref_location_key),
                context.getString(R.string.pref_location_default));
    }

    public static boolean isMetric(Context context) {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        return prefs.getString(context.getString(R.string.pref_units_key),
                context.getString(R.string.pref_units_metric))
                .equals(context.getString(R.string.pref_units_metric));
    }

    static String formatTemperature(double temperature, boolean isMetric) {
        double temp;
        if ( !isMetric ) {
            temp = 9*temperature/5+32;
        } else {
            temp = temperature;
        }
        return String.format("%.0f", temp);
    }

    static String formatDate(String dateString) {
        Date date = WeatherContract.getDateFromDb(dateString);
        return DateFormat.getDateInstance().format(date);
    }
}

I have put logcat in two files (Utility & ForecastFragment) to demonstrate the problem. Here is the logcat for the very first time when ForecastFragment onCreateView is loaded, when app is installed.

07-05 17:35:57.562 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:35:57.563 21604-21604/com.example.android.weather.app D/JIGAR: isMetric true

Here is the logcat when ForecastFragment onCreateView is loaded, after pressing Refresh button.

07-05 17:36:44.433 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.435 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.435 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.436 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.445 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.449 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.450 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.452 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.458 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.458 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.458 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.458 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.459 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.460 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.460 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.460 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.461 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.461 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.461 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.461 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.462 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.462 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.462 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.463 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.463 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.464 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.464 21604-21604/com.example.android.weather.app D/JIGAR: {}
07-05 17:36:44.464 21604-21604/com.example.android.weather.app D/JIGAR: {}

I have removed the Log.d statements for now from the above two locations but you are more than welcome to put log statements.

NOTE: This was a homework app with TO DO shown in the files and its complete application now but while playing with the application and understanding code, I found this bug in the application and trying to understand why its happening. This is NOT part of homework or any submission.

Thanks Dennis and Kaze, my android application is loading the default shared preferences from xml file with help of addPreferencesFromResource(R.xml.pref_general) in SettingActivity which extends PreferenceActivity which is a separate activity and it is started when someone clicks on Settings button hence the shared preferences are never populated before pressing settings button.

Your question about populating default preferences made me look into how is it populated and it should be populated in ForecastFragment onCreate using PreferenceManager.setDefaultValues as below ...

PreferenceManager.setDefaultValues(context, R.xml.pref_general, false);

This has solved my issue. I will be voting up your answers! Thanks

The reason why your sharedPreferences are empty is because you did not successfully input anything in the first place.

I suggest using sharedPreferences this way as this is what worked for me:

import android.content.SharedPreferences;

in your function:

SharedPreferences preferences = getApplicationContext().getSharedPreferences("AppPref", MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();

editor.putString("**Input your data here**", **tag your data/give it a name*);//example editor.putString("userPassword", password);

editor.commit();//this one places your string into the sharedpreferences instance you created

in order to access your shared preferences onCreate:

private String data; //this receives your data from sharedpref

@Override
protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    SharedPreferences conn = getSharedPreferences("AppPref", MODE_PRIVATE);
    data = conn.getString("**your data tag**","");
}

Hope this helps. You need to make an instance of sharedpreferences specifically for the data that is inputted during runtime if you wish to store values from your session. Im not so good at technical explanations so just ask for clarifications if you have any.

You should use following method from PreferenceManager to load the default values from xml (as you described in your comments that default values are loaded from xml).

static void setDefaultValues(Context context, int resId, boolean readAgain)

Sets the default values from an XML preference file by reading the values defined by each Preference item's android:defaultValue attribute.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM