I have an update that I want to run for my app based on what the previous version of the app is.
For example, if the user has version 1-5 and they are upgrading to 6 or 7 I want to run the update. Also, I don't want to run the update if they are upgrading from 6 to 7.
I've tried to accomplish this using a broadcast receiver that accepts the PACKAGE_REMOVE and PACKAGE_REPLACE intents but they don't seem to give me the information about the app that is being removed. I don't get the PACKAGE_REMOVED intent unless I'm reinstalling the same version. It's hard to tell because the debugger and the log print don't seem to catch before the intents are received. Any ideas?
#Override
public void onReceive(Context context, Intent intent) {
if (isOccurUpdated) {
return;
}
if (action.equalsIgnoreCase("android.intent.action.PACKAGE_REPLACED")) {
String name = info.versionName;
// We are assuming that we didn't get down here if we have already
// done this update
Log.d(TAG,"We receive the REPLACE intent "+name);
if (name.contains(OCCUR_ID_UPDATE) || name.contains(OCCUR_ID_UPDATE_FIX)) {
dbh.occurIdUpdate();
}
} else if (action.equalsIgnoreCase("android.intent.action.PACKAGE_REMOVED")) {
String name = info.versionName;
if (name.contains(OCCUR_ID_UPDATE)) {
isOccurUpdated = true;
}
}
}
You need to store previous-version information yourself somewhere, such as:
Step #1: Create a custom Application class, and define it in your manifest (android:name attribute of <application>).
Step #2: In onCreate() of your custom Application class, read in some persistent data structure that contains the version number of your app the last time onCreate() ran. For example, you could have a lastVersion value in your SharedPreferences.
Step #3: If you see that the last version is older than your current version, do whatever upgrade logic you want.
Step #4: Write your current version out to that persistent data structure (e.g., update the SharedPreferences).
If this is more tied to database logic, SQLiteOpenHelper handles all of this for you.
Related
I've been testing out a few sample weather app code projects looking for a 3 to 5 day forecast one that easily compiles in android studio. I can't find a simple one that is at my level of understanding yet but working with one that might help. (If anyone knows of a simple up to date weather forecast app then note it below please)
First line in question.
if (getSharedPreferences(KEY_PREF, 0).getBoolean(KEY_PREF, true)
&& servicesConnected())
From:
private static final String KEY_PREF = "firstrun";
if (getSharedPreferences(KEY_PREF, 0).getBoolean(KEY_PREF, true)
&& servicesConnected()) {
if (getSharedPreferences(KEY_PREF, 0).getBoolean(KEY_PREF, true)
&& servicesConnected()) {
// get current city lat lon
buildGoogleApiClient();
mClient.connect();
CommonUtils.showToast("Retrieving your current location...");
// this will be done one time only
getSharedPreferences(KEY_PREF, 0).edit()
.putBoolean(KEY_PREF, false).apply();
} else if (savedInstanceState == null) {
if (DBHelper.getInstance().getCityCount() == 0) {
// called only if the cities array is empty too add a city
getSupportFragmentManager()
.beginTransaction()
.add(R.id.container, new AddCityFragment(),
AddCityFragment.class.getSimpleName()).commit();
} else {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new WeatherFragment()).commit();
}
}
}
According to developer.android.com
boolean getBoolean (String key, boolean defValue)
Parameters: (1) String key: The name of the preference to retrieve. (2) defValue boolean: Value to return if this preference does not exist.
My original questions asked:
What exactly is getBoolean doing?
The documentation doesn't say what mode 0 means in the 2nd parameter of getSharedPreferences (String name, int mode). Any idea what?
How can I add a watch for getSharedPreferences to Android Studio's debugging
The Solution that I deduced with the help of you guys:
getSharedPreferences(KEY_PREF, 0) is looking for a KEY_PREF preference file on the android device. It returns an object to it which allows us to use getBoolean to determine if there is a key inside it with the same name. In this case it was looking for a "firstrun" file which doesn't exist during first run of the app on the actual android devices storage (saved as cache). Since the key didn't exist on the first run it sets the boolean value as true and continues running inside the if statement. If the key "firstrun" existed and it was set to false then it would return false and skip executing inside that part of the if statement.
Mode 0 is MODE_PRIVATE = the default mode, where the created file can only be accessed by the calling application (or all applications sharing the same user ID).
When I was trying to debug the issue I noticed that I can add a debugger watch for getSharedPreferences(KEY_PREF, 0) and drill inside the resulting preference file from withing the debugger to see that the mMap parameter to the SharedPreferences object creates a "firstrun" key with the value of "false". It was a first for me to see how it adds this value.
I also found a cool plug-in for android studio for mac and windows which is faster than going into the android device or virtual device and clearing the apps cache. It's called ADB idea and used with the CTRL+ALT+SHIFT+A hotkey combo.
Thanks everyone!
SharedPreference is just a way to store some small-size data.
1) Meaning why would it return true if it doesn't exist?
You can have it return false if you want. That's just the default value for that key. You should set default to true/false depending on the data logic. For their particular case, setting the default true for that key goes with their app logic.
For example, AFAIK android sdk doesn't provide us with any apis that would say that app is running for the first time. So, to know that if the app is running for the first time or not, you could get the boolean value for a key like "is_first_run" and have the default value be true. If the app is run for the first time, then this value would not exist in shared preference because you've not set it yet. So, the default value true makes sense. And if you find it to be true then set it to false, so that the next time the app is run, it'll return false. If you clear the cache, the value will be true once again because the cache is destroyed and the variable no longer exists.
2) question what is operating mode 0 for SharedPreferences getSharedPreferences (String name, int mode)
Operating mode 0 is just the PRIVATE_MODE. The developer should have used the variable instead of just setting it to zero. Their are multiple modes for the sharedpreference. You can look into the docs for more. Private mode means that this data can be accessed by this application only.
3) How can I add a watch for getSharedPreferences (Where is it stored?)
Not sure what you mean by adding a watch. Are you talking about an event listener on the sharedPreference? AFAIK, you can't do that. Also, shared preference is stored in app's cache directory which can be /data/app/appname/cache or something else (idk for sure)
Shared Preferences store data till you delete and reinstall the app
getBoolean( "this is the key" , true);
if getBoolean returns null then this whole statement evaluates to true
If getBoolean( "this is the key" , false);
This means that if it returns null then evaluate it to false
Meaning why would it return true if it doesn't exist?
This was your question right ?
It is the coder's choice to make it true or false like
At first when user has logged in when you did not even touch sharedPreferences
then it would probably return the second argument
like
if(SharedPreferences.getBoolean('isUserOld' , true)){
Toast.make( Context , "Welcome to Our App" , Long).show()
}
Here it is checking whether user is old or not
If user is not old then if blocks gets executed because the second param returns true
And this indicates that a new user has logged in
I have commented a few lines of code out in this function as I'm not sure what their purpose is. However when I run my android application everything work perfectly... so i assume I do not need them and they serve no purpose in my project.
private void deleteRoutine() {
myDb.deleteRoutineForCurrentDay(currentDay);
Intent intent = getIntent();
//intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
//finish();
//overridePendingTransition(0, 0);
startActivity(intent);
MediaPlayer mymedia = MediaPlayer.create(MondayRoutineEdit.this, R.raw.clearroutine);
mymedia.start();
}
}
public void deleteRoutineForCurrentDay(String selectedDay)
{
SQLiteDatabase db = this.getWritableDatabase();
db.execSQL("DELETE FROM " + RoutineTable + " WHERE DayOfWeek ='" + selectedDay + "'");
}
//intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
Here's what Intent says:-
If set in an Intent passed to Context.startActivity(), this flag will
prevent the system from applying an activity transition animation to
go to the next activity state.
You may wish to have a look at this Android - How to stop animation between activity changes.
I'd suggest that in normal circumstances omitting this is not an issue. However, the decision could depend upon how you cam about coding the above. If you basically copied an App, then there is likely good reason why the codes was used. if you are copying part of the code then the it could well be best to omit.
//finish();
If you use the above it will remove the activity from the stack, which could cause you issues e.g. using the back button WILL NOT take you back to this activity (as it now doesn't exist on the stack) and could be confusing to the end user. If you basically copied the entire App then it may have been included for a good reason. If you just copied the block of code or based your code on seeing this, then you would probably want to omit this.
You may wish to have a look at finish, along with the rest of Activity.
//overridePendingTransition(0, 0); this is discussed in the Android - How to stop animation between activity changes. link above. You may also wish to have a look at overridePendingTransition.
Whether to omit or not is basically the same and perhaps dependent upon the source of the code.
In my app, i generate and save private keys. They are encrypted using AES as described here (http://nelenkov.blogspot.com/2012/05/storing-application-secrets-in-androids.html). I've seen another entry on smiliar subject (http://nelenkov.blogspot.com/2012/05/storing-application-secrets-in-androids.html), but this solution gives no guarantee to work on all devices.
Everything is fine except one thing. Everytime usertime wants to get access to one of the encrypted files (or create new one) he needs to enter pin/password (which is very frustrating). I'm looking for a way to somehow avoid this, so as long as user don't leave aplication his password is stored.
The only solution i found is to load everything after user enters password and keep it in memory, but I don't think it's good one.
Cache your keys for a certain period of type (say 10 mins) and clear them afterwards. You can set alarms using AlarmService for this. There was a library project that does this for you, but I haven't used it personally:
https://github.com/guardianproject/cacheword
Why not create a custom Application subclass, and store the password there?
So something like:
public class App extends Application {
private static String sPassword;
public String getPassword() {
return sPassword;
}
}
Then use it:
String password = ((App) context.getApplication()).getPassword();
if(password != null && !password.isEmpty() {
// Decrypt files
} else {
// Prompt user for password, and save it to the Application
}
You'll also have to specify your custom Application in your manifest:
<application android:name="com.my.awesome.App" ....
When an automatic update occurs, the old date save in the preferences for my app gets delete.
How can i prevent this deletion?
I want that when my app auto updates , it does not delete my old data saved.
I am not sure whether this will be a good solution but just a suggestion you can try.
step 1: whenever you changeg (add/edit/remove) your data store it in permanent storage, you may try any of the following
a. save the data in file in sd card
b. store the data to your remote server or
c. store in internal memory of the phone.
(I am not sure whether it will persists after update at case c, for reference can check here
Step 2: creae a BroadcastReceiver that listens to the ACTION_PACKAGE_REPLACED Intent. So you know when your application package is updated. NOw read the data again from the storage where you saved the data ( either 1a/1b/1c)
Caution: It is not a good thing to save user data without his concern.
Android save your SharedPreference under /data/data/your package name/shared_prefs
Generally, update application won't delete your sharedPreference.
private void replaceNonSystemPackageLI(PackageParser.Package deletedPackage,
PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
String installerPackageName, PackageInstalledInfo res) {
...
// First delete the existing package while retaining the data directory
if (!deletePackageLI(pkgName, null, true, PackageManager.DELETE_KEEP_DATA,
res.removedInfo, true)) {
// If the existing package wasn't successfully deleted
res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
deletedPkg = false;
} else {
....
}
....
}
I think you need to check the following:
Is your device a rooted device? Apps on a rooted device can delete anything they want.
Did you use a different package name in that updated apk?
I have an app that depends on SQLite for data which is populated by xmls shipped with the app in the assets folder.
When you run the app the first time it sets a shared preference config_run = false.
then i check if config_run = false then parse the xml and dump the data into db
set config_run = true
Now i realize that when i have to push an update on Google Play and add more content into the XML. Even though i change the database version from 1 to 2. The import script wont run because shared preference config_run value will be set to true.
Any pointers on how to handle this situation ?
Scenarios
First Instal - Ver = 1, DB V = 1 (Data is parsed and dumped into the database)
Bugs Fixed and push and update but no data has changed - ver = 1.1, DB V = 1 (It should just replace the code and not upgrade or re-create the database)
Upgraded the DATA and pushed a new update - ver 1.2, DB = 2 ( No new code but data has to be re-created)
The Flow of My App
The App Starts Splash Activity. If Shared Pref - config_run is equal to false then it starts a Progress Dialog and parses and dumps the data into the database.
Upon Parsing and Creating DB and dumping data it goes to MainActivity.
Second Case
SplashActivity Runs and config_run = true so directly goes to MAin Activity.
As Suggested by few people below if i try to dumb the data into the database in onUpgrade of the SQLiteHelper it will happen only in MAinActivity as i dont open a Db connection in the SplashActivity and the Dialog Progress wont be displayed also.
Add a shared pref of the version number you last ran the script for.
On app start, check the current apk version, and if newer, run the script again and update the pref
Why dont you want use built in sqlite versioning system. DB version is independed from app version. And it does exactly what you want. SQLiteOpenHelper? Every time tou change your db version an onUpgrate callback will be called and you can refill your db. There are a lot of examples.
Instead of setting your shared pref (config_run) to false and making it true, just set the database version into it. When you update your app, check whether you have the same version number in your shared pref. You can do this as shown below:
configRun = settings.getInt("database_version", 0);
if ((DBAdapter.DATABASE_VERSION) == configRun)
{
//skip xml parsing
}
else
{
//first time configRun will be "0" and DBAdapter.DATABASE_VERSION will be 1
// so you need to parse your xml here and set configRun =1
//on update, change your DB version to 2. now again your configRun and DBAdapter.DATABASE_VERSION will mismatch and you can parse your xml.
}
Have your xmlfiles end with the date of the update, and store the last updated date in sharedpref.
On launch you can check search for updates ( in an optimized way ) and if you find a new file with a new date when compared to the last time you know you need to dump the file.
Total hack job :D
Two things you can do:
The right way: Override database provider's onUpdate() to import the file. (as suggested above)
The one line changer: Instead of check for key="config_run", you check and set for key=("config_run"+DB_VERSION) to see if import is needed, and of course, if the key does not exist, you should return false.
This way every time you update the DB number, import job will run again.
This is agnostic to your app version.