In order to initialize preferences with default values from XML file describing the preferences, I can call PreferenceManager.setDefaultValues(this, R.xml.preference, false). Sounds simple, but I'm not quite sure when exactly should I call this?
As I understand from the docs, the above call is only needed once, in situation when no preferences are set yet. As a result of this call, preferences residing in /data/data/<myapp>/shared_prefs are going to be set, so all subsequent attempts to read preferences will get me the default values. Logically, setDefaultValues should be called in every single code path that might be executed without preferences being already initialized. Over time, this turned out to be multiple places - main activity, another activity, background service, small BroadcastReceiver handling system messages... Right now I've put call to setDefaultValues in onCreate() for my Application object, as I'm already using it as convenient singleton for other things.
Questions:
Do I have a guarantee that every time my code executes, Application object will be created and onCreate will run?
How are you dealing with this problem? One other way would be to hardcode default values into getFoo(key, defValue) calls, but that effectively scatters your default settings across whole code.
EDIT: Essentially, I don't know which solution is worse: calling setDefaultValues every time I access prefs in given code path, or calling it in some common place (like app's onCreate) every time, no matter whether I need it or not.
I'm going to delete my original answer and answer the questions that you actually asked.
Yes, the Application object's onCreate will be executed at the start of every process. Keep in mind that doesn't guarantee it will be run each time you start your main activity. If Android still has your process running it will use that again (e.g. you still have a service running). So yes what you're doing will work and you're correct in observing it won't blow up.
I'm dealing with this problem by subclassing SharedPreferences (let's call it MyPrefs -- that's not what I call it but that's not important). The key features of MyPrefs are:
encapsulation of get/set methods instead of directly accessing the key names
Handling code for loading defaults. I'm being a little lazy by using a static boolean instead of an AtomicBoolean to tell me if the defaults have been loaded.
Having said that... it works for me, but if you're almost certain you'll be calling the SharedPreferences every time your code runs where you're at works as good as any.
Hope this helps more than my previous answer.
Related
In my main activity I read some preferences
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
fontSize = settings.getFloat("textsize", (float) 20);
userName = settings.getString("userName","");
}
In various other sub-activities I reference these variables like this
body.setTextSize(Main.fontSize);
This works fine most of the time. Occasionally after the app has been running for a while and I relaunch it, all the configuration variable appear to be empty. It is erratic and hard to reproduce when debugging. When it happens, I can hit back repeatedly through all the old screens until it exits, then relaunch and the preferences are back and working
I could randomly try moving things around into different places like onStart(), or onRestart(), which hasn't helped so far. Or redo every activity to reread the shared preferences when it starts. but I'd prefer to understand what about the lifecycle I've gotten wrong that is causing this problem.
Android regularly destroys your Activity and recreates it when needed.
Therefore, your onCreate() function is reading the saved SharedPreferences values, but the class variables fontSize and userName are not staying in scope.
I believe that the problem seems random and is exasperated because these variables appear to be static. I say this because your example accesses fontSize in a static way:
body.setTextSize(Main.fontSize);
You should save the values whenever they are changed or in onPause(), as you probably already do. But, reading them only in onCreate() is not enough because Android is not always going to completely destroy your Activity. So, onCreate() will not be called every time your Activity resumes. You must re-read the values either whenever you need them, or in onResume();.
I wrote 'exasperated', above, because static variables are not automatically cleared by Java unless the class is completely destroyed. Therefore, sometimes the values will stick around longer than other times making the behavior look unpredictable.
Finally, it is not best practices to allow these values to be static. There are many other ways to share them between Activites, including SharedPreferences.
I have some code that works 98% of the time, and 100% during my own testing so I can not really reproduce the problem other than having user devices experience this issue.
What I do in onPostExecute() is set a parameter like this:
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( AddProblemActivity.this);
prefs.edit().putString("recent_problem_id", result ).commit();
and then go to the next activity:
Intent myIntent = new Intent(AddProblemActivity.this, ProblemActivity.class);
AddProblemActivity.this.startActivity(myIntent);
and then try to get that parameter there like this:
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
ProblemActivity.this);
// display a loading message before problem loads.
String recent_problem_id = prefs.getString( "recent_problem_id" , null );
if ( recent_problem_id == null )
{
// Sometimes it is null!
}
Would anyone know why this happens?
Thanks!
If you are trying to pass the data to a new activity, why not put it as a String extra in the intent? Then, get that String from the intent in the new activity. If you still need to store it, you can do so in the new activity's onCreate() after it pulls it from the intent.
Intent myIntent = new Intent(AddProblemActivity.this, ProblemActivity.class);
//Add results here
myIntent.putExtra("RecentProblemId", result);
AddProblemActivity.this.startActivity(myIntent);
Then, in the onCreate of your new Activity, do:
String recentProblemId = getIntent().getStringExtra("RecentProblemId");
Now, if you still need this information stored, do:
if(recentProblemId != null){
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
ProblemActivity.this);
prefs.putString("recent_problem_id",recentProblemId).commit();
}
I know this doesn't exactly answer your question as to why the String isn't always being committed to the preferences in onPostExecute(). However, the best practice for passing information between activities is via Intents and extras.
My guess as to why it may not always work for some users, is that their devices are not done writing the data to the shared preferences file before the new Activity starts and tries to read from that same file. Hope this helps.
First of all, see Raghav Sood's answer.
There is one delicate moment. You might start executing your AsyncTask than rotate the device. Activity will be recreated and in PostExecute you'll have wrong context so your preferences won't be saved.
If it's true you should use onRetainNonConfigurationInstance() to save appropriate task's instance.
I'm not sure about this, but I think the problem might be due to the difference in the Context you're passing. You're using the Context of AddProblemActivity first, and then the Context of ProblemActivity. Try using a set preference, like a filename one:
SharedPreferences prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE);
Note that getSharedPreferences() is a method from Context, so you'll need to have a reference to an Activity or maybe Application Context in your AsyncTask to be able to use it.
Seems like your code is fine and there's no reason why it shouldn't work, the only reason will be some defect which is probably device related. My thoughts are since the Shared prefs are saved on local storage something can go wrong in the process.
As advised in the comments, it will be a must to add a device type log,
I would recommend you use "ACRA" - http://code.google.com/p/acra/ that can give you detailed reports with minimum effort (pay attention you can send a report not necceserily only on app crash).
Take a look at this thread, he shows a problem that maybe you are experiencing as well:
SharedPreferences will not save/load in PreferenceActivity.
If it's the case, a solution will be to handle the saving of this persistent data manually on the local storage or use a DB solution. Good luck :)
I think the issue is due to the shared preference setting not being written to your file system yet when you try to access it from your second activity. You mention that you write the setting from the onPostExecute method (of an AsyncTask perhaps?). When you start an AsyncTask there is no guarantee that it will start immediately. The only guarantee there is, is that it will start in a background thread. The platform can and will decide when to actually run the background thread, depending on system load, file system blocks or so. It might very well be so that your AsyncTask hasn't been started yet when you switch to the second activity (and hence the onPostExecute method hasn't been called yet).
Saving shared preferences are tricky as they write to the file system. You might end up blocking the main thread if you aren't careful. The SharedPreferences.Editor object has an apply method as well which will update the in-memory cache of your shared preferences immediately (making the changes available at once) and kick of a background thread to save the actual value to the file system as well. So my tip would be that if you have the possibility, you should try to call the apply method (from your main thread) instead of the commit method from (what I assume) a AsyncTask. The apply method requires API level 9 or higher.
For your reference: http://developer.android.com/reference/android/content/SharedPreferences.Editor.html
Edit:
The commit method will return a boolean value depending on the result of the write operation. You could (should?) check that return-value in order to at least be able to take correct counter measures on failure (like show a "Couldn't save your setting, please try again" toast or something).
Cheers,
--dbm
I could be wrong. But I believe a friend of mine had a similar problem once. I was stuck on this problem for hours. The results for more like 30% of the time it wouldn't work. I believe that the onPostExecute() runs on a separate thread when the Intent is instantiated and the activity is called. This is because an AsyncTask is implemented on a separate thread. Depending on the device, this would be called more likely then not. Our tablet it rarely happened, on the smart phone it would occur more commonly.
You can test this by debugging the application and looking at the AsyncThread thread and see when the call is made.
Yes, it is better to send the variable via the putExtra().
I hope this helps you understand why this occurred.
The commit action of SharePreferences is synchroneos so I don't think the fact that you are starting the new intent should effect it, only thing is that the commit action isn't fail safe, it can fail.
http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#commit()
Returns true if the new values were successfully written to persistent
storage.
Maybe you should check that return value to make sure you managed to save the result.
So after a while of programming android apps(1 uploaded on market, have 3k+ active installs with a 4,7 rating), I started to wonder, how can I make my app even more awesome. I realized, that I couldnt really add any really new, and world changing features, so I started to inspect the performance, and how to optimize just about everything, how to find the best cpu/memory usage ratio, and so on.
Anyway, I found out that onCreate will run in the case of rotating the screen, which is quite logic, but there are some(big) calculations, that I surely dont need to redo after every rotate. One of this is iterating through a csv with 6500 rows, having 4 columns, 2 of it always contains some data, 2 of it not always. The 2 column with datas will be used for autocomplete adapter, the another 2 is optional for a feature, but it still need to be initialized. Currently, this is running in an asynctask, triggered at the end of the onCreate, and takes about 3 seconds on my HTC Desire S, which has a quite good CPU, so lower budget devices will have a longer initialize time after every rotate which is surely not I want... It wont crash the UI, but there won't be any autocomplete until thoose seconds are over.
SO: my question is, can I do this in some separate method, for example a constructor(like in standard java), or is it a bad practise, because the special lifecycle of activities? I mean, I instantiate my activity the way the "constructor" will run, and just after that, my onCreate will run. In case of rotating, my "constructor" won't run again, but the onCreate will. Stability will still be my nr1 goal. Or, is there any good way to do this? Something that is created for exactly like this, which im unaware of? I really want to improve a lot in this matter, and I would really appreciate some help in this, preferrably from ones with experience in this, but any help is welcome! :)
For example, if I want to make a new activity this way, I would do it something like this:
new MyActivity(some parameters);
so the constructor runs, which ends something like this:
startActivity(new Intent(context, MyActivity.class));
So this way, the constructor runs, my variables will be initalized(not connecting to any view etc), and after that, my activity can run its onCreate variable anytime it has to.
Pardon me if Im wrong the syntax, I just fasttyped it :)
You should decouple this logic from your activity. There are many ways to do this, but the end goal is to have your csv parsing done in a different class, and this class should expose information about whether or not the data has already been parsed. So, in onCreate, you call your class to get the data. If it already exists, you get your cached data immediately. If this is the first time the method is called or for some reason your cache has been cleaned up, you parse your csv file and do whatever calculations you need.
you could take a look at onRetainNonConfigurationInstance which can return an object which you can access after your activity has been recreated.. so you would simply return an object containing all your processed results of onCreate and the next time around you check if there is a getLastNonConfigurationInstance() - and don't recalculate everything
My question is can i get to know when the entire application gets paused/resumed start/stop etc.
For example if i have 5 activities in my application. Whenever any activity gets paused/resumed android notify the activity by calling the onPause/onResume methods.
So there are two possible scenarios when my activity gets paused.
1. My activity-2 gets paused because my activity-3 gets invoked.
2. My activity-2 gets paused because of some outside activity like incoming call.
Here I am interested only tracking when my activity gets paused by outside activities not my own application activities.
So is there any android provided solution for this or do I have to write my customized solution.
Thanks
Dalvin
There is no solution provided by the API because it is not needed in most cases.
What you can do is to create an abstract activity and make all your activities inheriting from this abstract one.
In this abstract activity, by overriding onCreate, onResume, onPause, onDestroy, you can manage to count how many of your own activities are "alive", and then determine the state of your application.
This could work but it's not really the Android phylosophy
You can know the starting of the whole application on application.oncreate() but there is no indicator for the whole application pause. Most of the cases never needs it anyway.
So further read in the activity lifecycle and the application class.
Still you can do this option in your program by overriding the onPause in each class and save a value to the sharedPrefrences then check on this value all over the application
If I understand your question, you want your app to be able to distinguish between exiting the current activity within the context of your program or by an external event like a phone call. I have used the following method in the past to do this (although it may not be the best, it definitely works):
(1) Set up a value in SharedPreferences (the built in file for storing a program's data). Call it something like "exitStatus" which is set to 1 for an exit within the program code and 0 for an exit based on external events.
(2) Now, within each of your activities, set the value of exitStatus to 0 in onResume (which is called no matter how you enter). If your program exits due to an external event within that activity, this value will persist when the program is reloaded.
(3) At the end of your activity, at all points where you are going to transfer to another activity, first set exitStatus to 1. Then, when you arrive at the other activity, it will know that you arrived there from within your program.
(4) Thus, just to be clear, each of your activities can check exitStatus at the outset to see whether you are entering from within your program context (= 1) or after a non-local exit of some kind (= 0).
That's all there is to it. I use this method to be sure that load data for my app is present as it may be lost if a user turns off their device so that the app tries to pick up in the middle of things when they later reboot, etc.
Instead of making base activity and override onPause/onResume you can use
registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback)
where you can handle these states for application activities in one place.
I know Android's Activity model is a bit different from what I usually consider to be an "app".
I want to do something (in this case, check some notifications on a server and show them if available) when my app is "launched". What is a good way to accomplish this?
I likely don't want to do it in an activity's OnCreate, since each activity can be created any number of times - the code would get called more often than necessary.
The app also has multiple entry points - would I have to duplicate the check in each activity?
What I'm thinking of doing is setting up this code inside the Application object, along with a flag that tracks whether it's already been called - and just call it from each Activity's onCreate().
Is there a better or more "proper" way to do this?
The right, Android-approved way to do this is:
Create your own android.app.Application class
Override the onCreate method
In the AndroidManifest.xml, change the android:name attribute of the application element to the name of your class
Now, whenever your app is "started" (any one of your activites is started for the first time and no other instances are alive) onCreate will be called.
You may also find the onTerminate method useful.
Can you just check if the bundle passed to onCreate() is null?
It's not null "If the activity is being re-initialized after previously being shut down..."
There's probably no harm in putting it in onCreate; the Activity is really only destroyed when the OS needs the RAM for something else, not when the user goes to another app.
EDIT: You can also have a Service that runs when the device gets booted up, too. This might be a better option if you also want to check when the app starts, since you'll only have to call context.startService from the Activity to run the check. Just be sure to stop it when it's done if you don't need it to be persistent.