Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PIE Only: SharedPreferences IllegalStateException

Tags:

android

We're getting an IllegalStageException when trying to initialize our shared preferences singleton class but I don't know what's causing it.

Can anyone here advise me as to what may be causing this?

NOTE: This is ONLY happening on Pie

Here's the report from the Google Play Console (crash is not showing in Crashlytics):

java.lang.RuntimeException: 
  at android.app.ActivityThread.handleBindApplication (ActivityThread.java:5876)
  at android.app.ActivityThread.access$1100 (ActivityThread.java:199)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1650)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loop (Looper.java:193)
  at android.app.ActivityThread.main (ActivityThread.java:6669)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:858)
Caused by: java.lang.IllegalStateException: 
  at android.app.ContextImpl.getSharedPreferences (ContextImpl.java:419)
  at android.app.ContextImpl.getSharedPreferences (ContextImpl.java:404)
  at android.content.ContextWrapper.getSharedPreferences (ContextWrapper.java:174)
  at com.nbc.news.utils.SharedPreferences.<init> (SharedPreferences.java:27)
  at com.nbc.news.utils.SharedPreferences.init (SharedPreferences.java:44)
  at com.nbc.news.NbcNews.onCreate (NbcNews.java:122)
  at android.app.Instrumentation.callApplicationOnCreate (Instrumentation.java:1154)
  at android.app.ActivityThread.handleBindApplication (ActivityThread.java:5871)

Here's the init code:

private android.content.SharedPreferences sharedPreferences;

    private static SharedPreferences instance;

    private SharedPreferences(Context context) {
        Context appContext = context.getApplicationContext();

        sharedPreferences = appContext.getSharedPreferences(context.getString(R.string.database_name), Context.MODE_PRIVATE); // <- this is line #27
    }

    public android.content.SharedPreferences getPreference(){
        return sharedPreferences;
    }

    public static SharedPreferences getInstance() {
        if (instance == null) {
            throw new RuntimeException("SharedPreferences must be initialized with context prior to use");
        }

        return instance;
    }

    public static void init(Context context) {
        if (instance == null) {
            instance = new SharedPreferences(context); // <- this is line #44
        }
    }
like image 224
Psest328 Avatar asked Dec 17 '18 21:12

Psest328


Video Answer


2 Answers

I only found one or two puzzle pieces which may be helpful: the source code of ContextImpl has two IllegalStateExceptions, one of them from a method getSharedPreferences() in line 426-454.

"SharedPreferences in credential encrypted storage are not available until after user is unlocked"

This IllegalStateException is thrown from an if block which is executed only if the target SDK version is Oreo or higher

if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
    if (isCredentialProtectedStorage()
         && 
        !getSystemService(UserManager.class).isUserUnlockingOrUnlocked(UserHandle.myUserId())) {

        throw new IllegalStateException("SharedPreferences in credential encrypted "
                                + "storage are not available until after user is unlocked");
     }
} 

Searching the web for isCredentialProtectedStorage() led me to FileBasedEncryption.

Since I don't know the details of your app's workflow I can only suggest to make sure that the app does not try to access a secured file while the device is locked.

like image 66
Bö macht Blau Avatar answered Sep 25 '22 11:09

Bö macht Blau


You must be trying to access SharedPreferences after LOCKED_BOOT_COMPLETED and before BOOT_COMPLETED which isn't allowed since Nougat / API 24 unless you migrate your preferences to the Device Protected Storage.

2 options here:

  • Either remove the LOCKED_BOOT_COMPLETED action and the android:directBootAware flag from your Broadcast Receiver. This way your app will wait till BOOT_COMPLETED (when the user unlocks the device) before trying to access SharedPreferences.
  • Or if you do need to access SharedPreferences after LOCKED_BOOT_COMPLETED and before BOOT_COMPLETED, make sure your migrate your preferences to the Device Protected Storage and use Device Protected Storage Context when calling getSharedPreferences().

More details on the second option in the official docs: Support Direct Boot mode

like image 43
Maksim Ivanov Avatar answered Sep 25 '22 11:09

Maksim Ivanov