GenError
GenError

Reputation: 1186

Storing URI as String into SharedPreferences causes Formatter exception

I have a strange behaviour when I try to store the URI of a directory as a String in SharedPreferences:

First I start an intent to get a directory

val i = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
i.addCategory(Intent.CATEGORY_DEFAULT)
startActivityForResult(Intent.createChooser(i, "Choose directory"), REQUEST_TARGET_FOLDER)

Then in onActivityResult I try to store the result as a string (data is the Intent)

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// checks left out for simplicity
val targetDir = data.data.toString()

val storagePreference : ListPreference? = findPreference(resources.getString(R.string.settings_storage))
storagePreference?.summary = "External: $targetDir"
}

When I then select a folder (for example Downloads/Rec) the intent returns content://com.android.externalstorage.documents/tree/primary%3ADownload%2FRec as Uri and when I convert it to String and try to set the summary as shown above, the App crashes:

   java.util.UnknownFormatConversionException: Conversion = 'F'
        at java.util.Formatter$FormatSpecifier.conversion(Formatter.java:2782)
        at java.util.Formatter$FormatSpecifier.<init>(Formatter.java:2812)
        at java.util.Formatter$FormatSpecifierParser.<init>(Formatter.java:2625)
        at java.util.Formatter.parse(Formatter.java:2558)
        at java.util.Formatter.format(Formatter.java:2505)
        at java.util.Formatter.format(Formatter.java:2459)
        at java.lang.String.format(String.java:2870)

The reason are the Url encoded parts in the string, because if I replace '%2F' by '/' the storing works.

The storagePreference?.summary = "External: $targetDir" is the failing line. Why can't the summary string contain "%F2" or similar strings?

Upvotes: 0

Views: 69

Answers (1)

CSmith
CSmith

Reputation: 13458

According to ListPreference source code (see below) the summary property is anticipating a format string, in fact you can see how getSummary() is using String.format, which will choke on the encoded characters in your URI.

It seems the suggestion to decode the Uri string is a reasonable solution.

/**
     * Returns the summary of this ListPreference. If the summary
     * has a {@linkplain java.lang.String#format String formatting}
     * marker in it (i.e. "%s" or "%1$s"), then the current entry
     * value will be substituted in its place.
     *
     * @return the summary with appropriate string substitution
     */
    @Override
    public CharSequence getSummary() {
        final CharSequence entry = getEntry();
        if (mSummary == null) {
            return super.getSummary();
        } else {
            return String.format(mSummary, entry == null ? "" : entry);
        }
    }

/**
     * Sets the summary for this Preference with a CharSequence.
     * If the summary has a
     * {@linkplain java.lang.String#format String formatting}
     * marker in it (i.e. "%s" or "%1$s"), then the current entry
     * value will be substituted in its place when it's retrieved.
     *
     * @param summary The summary for the preference.
     */
    @Override
    public void setSummary(CharSequence summary) {
        super.setSummary(summary);
        if (summary == null && mSummary != null) {
            mSummary = null;
        } else if (summary != null && !summary.equals(mSummary)) {
            mSummary = summary.toString();
        }
    }

This wasn't obvious reading the developer docs for ListPreference though...

Upvotes: 1

Related Questions