Android Context Change on update? - java

I have a Native Android App in the Store. This App has its Settings stored in the SharedPreferences. Now I am working on an Update for the App. The Update is a Xamarin Cross-Platform App. App Name, Package Name, and those things are the same as in the Native App. But I can't get the values from SharedPreferences now. Did I make something wrong? I guess it's because of a different Application Context. But I don't know why.
Here is how the Native App is loading the Values:
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
final Set<String> keySet = prefs.getAll().keySet();
for (final String key : keySet) {
if (key.startsWith(DatabaseSetting.DATABASE_SETTING_KEY_PREFIX)) {
//Do stuff
}
}
After updating the App I can not see any keys in the prefs.
C# Xamarin Code to also load these prefs:
ISharedPreferences sharedPrefs = PreferenceManager.GetDefaultSharedPreferences(Application.Context);
foreach (string key in sharedPrefs.All.Keys)
{
if(key.StartsWith(DATABASE_SETTING_KEY_PREFIX))
}
The App was updated without uninstalling the old one. Just deployed the new one.

Is it giving you a nullpoint?
Are you storing app context (Android.App.Application.Context) or class context(Context)?
Are there any other exceptions thrown that you possibly aren't catching?

Related

SharedPreferences key setted before creation

I found a problem with Shared Preferences which happens only for some devices and only with Android 9 (Pie) o above.
To encrypt my database I create a key and store it to my sharedPreferences.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String content = sharedPreferences.getString(-nameOfTheKey-, "");
if (content == null || content.isEmpty()){
// Create and store the new key
content = restoreApplicationKey(context);
}
As you can see, first I check for the availability then I create if not exist or empty. This key isn't created in any other place. That is the only point.
When I try to first install the app on my device, the condition return false because the key-value is already present in my SharedPreferences. This is impossibile!
I found online that the solution is to set allowBackup="false" into the Manifest and reinstall the app but I don't understand this behavior. How is it possible that the kay-value is pre setted?
Also, before setting allowBackup to false, I noticed that if forcing the initialization and uninstall the app, after the reinstall the key is still present but the value is changed. Every time with the same string but different from the saved one. All others key are fine, except this one.
As I said, the above code is the only entry point and it's called only at the app launch. Also the restoreApplicationKey(context) is called only at this point.

Why are SharedPreferences ignored on some devices?

I have a 2-step database migration in my app, using Room. The migration had to be done in two parts (v.2 -> 3 -> 4) because complex database operations using newly added tables had to be done between versions 3 and 4.
I have a SharedPreferences key-value pair to save the information when the user migrates. This is to ensure that the app won't try to migrate from 2 to 3 after it has already migrated to version 4.
The migration from 2 to 3 is meant to be run only when the user updates the app from the old version to the new version.
This has worked in every single one of our test devices, but Google Play's data is giving us a lot of crash reports for IllegalStateException, stating that the database tried to downgrade itself. This should NOT happen, should the SharedPreferences check work correctly.
For reference. Here's some of the migration code:
if(!hasMigrated){
if(NeedMigration.needMigrate(MyApplication.getAppContext())){
..
try{
//instantiate migration 2 to 3
pdb=PreDatabase.getInstance(this);
dRepo = new DayRepo(pdb, this, dayInstance);
//db operations
createDays();
//store into SharedPrefs that we already migrated from 3
NeedMigration.setHasMigrated(MyApplication.getAppContext());
}catch(IllegalStateException e){
Log.e("Database", "Already at version 4");
..
return;
}
..
//instantiate migration from 3 to 4 (mig is in db.build)
mdb = MyDatabase.getInstance(this);
mRepo = Repository.fromContext(this);
..
} else {
/*
If the user already has updated and the database exists in its newest state, skip straight to MyDatabase.java
instantiation and load Day objects from database.
*/
instantiateDatabase();
}
..
hasMigrated=true;
}
The hasMigrated boolean is just a static boolean so that the app won't try to instantiate the database again during runtime. If NeedMigration.needMigrate returns false, it instead just instantiates the database at the current version.
Here is the NeedMigrate class:
private static final String MIG_PREFS= "migration_preferences";
private static final String MIG_HAS_UPDATED ="has_updated_to_four";
//if yes (true) then yes need migrate
//if false then no! no migrate
public static boolean needMigrate(Context activity){
//compare versioncode with sharedprefs
final SharedPreferences preferences = activity.getSharedPreferences(MIG_PREFS, Activity.MODE_PRIVATE);
final boolean needmig = preferences.getBoolean(MIG_HAS_UPDATED, true);
return needmig;
}
public static void setHasMigrated(Context activity){
final SharedPreferences prefs = activity.getSharedPreferences(MIG_PREFS, Activity.MODE_PRIVATE);
//check if value exists
SharedPreferences.Editor edit = prefs.edit();
//false because if needMigrate is false then we dont need migrate
edit.putBoolean(MIG_HAS_UPDATED, false);
edit.commit();
}
Again let me clarify that this works perfectly fine on my test devices, but we're getting a lot of crash reports from Google implying that the database tried to run at the previous version:
java.lang.IllegalStateException:
at androidx.room.RoomOpenHelper.onUpgrade (RoomOpenHelper.java:101)
at androidx.room.RoomOpenHelper.onDowngrade (RoomOpenHelper.java:113)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onDowngrade
(FrameworkSQLiteOpenHelper.java:144) at
android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked
(SQLiteOpenHelper.java:257) at
android.database.sqlite.SQLiteOpenHelper.getWritableDatabase
(SQLiteOpenHelper.java:2) at
androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase
(FrameworkSQLiteOpenHelper.java:96) at
androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase
(FrameworkSQLiteOpenHelper.java:54) at
androidx.room.RoomDatabase.query (RoomDatabase.java:256) at
androidx.room.util.DBUtil.query (DBUtil.java:54) at
fi......controllers.database_controller.MealGroupDao_..PreDatabase_Impl.getMealGroupsInDay
(MealGroupDao_..PreDatabase_Impl.java:157) at
fi.....DayRepo.lambda$populateDayCache$0 (DayRepo.java:301) at
fi.....controllers.database_controller.repos.-$$Lambda$DayRepo$gQGLM0kHLZLeLseqjFzeMTTO2fw.run
(-.java:8) at java.util.concurrent.ThreadPoolExecutor.runWorker
(ThreadPoolExecutor.java:1167) at
java.util.concurrent.ThreadPoolExecutor$Worker.run
(ThreadPoolExecutor.java:641) at java.lang.Thread.run
(Thread.java:764)
We just got a review saying "Can't open app"and I suspect that this is the reason.
Why is this SharedPreference check ignored, or does it just return a wrong value ? Is this a context-related issue or perhaps related to the fact that the NeedMigration class is static?
I have tried:
Changing context from activity to application context
moving the setHasMigrated call to happen before the database 2 to 3 update (DID NOT WORK...)
Adding try/catch blocks in a lot of places

While testing my app that uses shared preferences in some mobile phones it doesn't supports multiple shared preferences? What is the reason for this?

The problem is that my application that uses multiple shared preferences that fails to resolve data from shared preferences on certain devices. On the installation it works correctly and it has a problem when re launching the application on certain devices.I want to know what is the reason for it?
SharedPreferences SchoolDetails = getSharedPreferences("schooldetails", MODE_PRIVATE);
String orgid = SchoolDetails.getString("Orgid", "");
SharedPreferences userDetails = getSharedPreferences("userdetails", MODE_PRIVATE);
String Uname = userDetails.getString("Username", "");
String pswrd = userDetails.getString("pass", "");
Standard preferences do not support error handling. Try another implementation https://github.com/yandextaxitech/binaryprefs

Getting Preferences API working on both Android and PC

I'd like to save user preferences from a sketch running either on a PC or an Android phone, using the same code in as standard "Java-way" as possible.
Ideal candidate for my purposes seems to be the java.util.prefs.Preferences class. So, I wrote a little test script to see if it's working in processing:
String prId = "counter";
Preferences prefs = Preferences.userNodeForPackage(this.getClass());
int counter = prefs.getInt(prId, 0);
println(counter);
prefs.putInt(prId, 1+counter);
This program outputs an increasing number each time it is executed - on a PC. On Android, the default value (0) is always shown.
Are there any additional steps required to get this working on Android? Permissions to be requested?
Are there any alternatives for saving name - value pairs on both platforms?
Obs.: "WRITE_EXTERNAL_STORAGE" permission is already enabled
This is a known Android bug unfortunately: https://code.google.com/p/android/issues/detail?id=22232
Basically the app does not have write permissions for the generated path, which should be a system-specific location where the user has write permissions instead.
Simplest (cross platform) workaround could be to use java.util.Properties, where you have control over the storage location.
Another workaround (if you are tied to the Preferences API for some reason) might be to provide your own implementation of AbstractPreferences (perhaps backed by Android SharedPreferences?), see this SO post: Is there a way to use java.util.Preferences under Windows without it using the Registry as the backend?
P.S.: Another workaround option might be to explicitly export / import the data using Preferences.exportSubtree(OutputStream os) and
Preferences.importPreferences(InputStream is)
In Android, the preferred way to store preferences is to make use of SharedPreferences. The equivalent code would be like this:
String prId = "counter";
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
int counter = prefs.getInt(prId,0); // Get int, default is 0
SharedPreferences.Editor prefsEditor = prefs.edit();
prefsEditor.putInt(prId, counter++); // Put counter back
prefsEditor.commit(); //Don't forget to commit the changes
I don't know exactly why java.util.prefs.Preferences would fail in Android. I suspect the reason is data would be deleted after the current Activity or the application is destroyed.
Because directory structure is different on each platform, it's hard to get preferences by just using one single same method. For example, data is stored in /.java/.userPrefs/**yourpacakgename**/prefs.xml on Android while it's in Registry on Windows and in ~/Library/Preferences/**yourpacakgename**/prefs.xml on Mac OS X. Also, you can't use Preferences.userRoot() in Android because an application cannot get root access.

Why are my SharedPreferences entries not being cleared upon reinstalling?

Upon reinstalling my app the code within my if statement is being accessed despite my SharedPreferences entry not yet having been created. I'm using an emulator with eclipse, does the data need to be cleared some other way than reinstalling? Thanks
prefs = getSharedPreferences("appData", 0);
Gson gson = new Gson();
String gsonStr = prefs.getString("playerString", null);
if(gsonStr != null)
{
//This code is being accessed on the apps first onCreate() call prior to being reinstalled
Player[] tempArray = gson.fromJson(gsonStr, Player[].class);
Log.d("First Player", "" + tempArray[0]);
}
protected void onPause()
{
super.onPause();
if(savedPlayers != null)
{
Gson gson = new Gson();
String gsonStr = gson.toJson(savedPlayers.toArray());
prefs = getSharedPreferences("appData", 0);
SharedPreferences.Editor editor = prefs.edit();
//editor().clear();
Log.d("GSONString",gson.toString());
editor.putString("playerString", gsonStr);
editor.putInt("ArraySize", savedPlayers.size());
editor.commit();
}
}
Most likely the app is simply being reinstalled. In order for the data to be cleared, the app actually has to be uninstalled first. Reinstalling won't actually clear the data. The emulator actually allows for a runtime option that will wipe all user data when you run the emulator, but I don't believe it runs each time your run the app. You can set this option in the Run Configurations for the app if you are using eclipse.
To do it from inside the emulator, go to settings->applications->manage applications->your app->clear data
A shared preference and sqlite db doesnt get removed on a reinstall. To delete them, go to Settings-->Appications-->Manage Applications-->Click on you application-->Click on "Clear Data" on emulator. this will clear the data stored.

Categories