4

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!

Marcin Orlowski
  • 72,056
  • 11
  • 123
  • 141
Genadinik
  • 18,153
  • 63
  • 185
  • 284
  • The devices where it happens - do they have something in common? Like Manufacturer, API version, anything ? – Fildor Sep 26 '12 at 14:56
  • @Fildor I have not been able to reproduce this on any device myself, and I am actually not sure whether it is possible to get the device info from code inside an activity..is is possible for me to detect info about their devices when this type of problem happens? – Genadinik Sep 26 '12 at 15:04
  • There are libraries that can give you that feature, yes. But anyway: Someone must have been telling you, they have this issue? – Fildor Sep 26 '12 at 15:11
  • @Fidor I make the app email me in case the variable is null...as kind of a custom way i made for myself to detect this problem – Genadinik Sep 26 '12 at 15:13
  • I see ... we are using a library, so I don't know _how_ they do it, sorry. – Fildor Sep 26 '12 at 15:14
  • 1
    Wait a minute ... of course I know: android.os.Build there you can get the Version the user is using, hardware and more http://developer.android.com/reference/android/os/Build.html – Fildor Sep 27 '12 at 06:32
  • Have you checked this post: http://stackoverflow.com/questions/4693387/sharedpreferences-and-thread-safety (in particular the bug report) – assylias Sep 29 '12 at 10:37
  • Could you please post the whole `onPostExecute` method of the `AsyncTask` that's trying to save the shared preference? – dbm Oct 02 '12 at 20:23
  • 1
    Not solving your problem, but are you using SharedPreferences just to send the id to the next activity? Can you not send it as an extra in the intent? – Sameer Oct 04 '12 at 11:47
  • @Sameer that is what my answer suggested. Genadinik has yet to reply and let me know why he can't do that. – dennisdrew Oct 04 '12 at 12:33
  • Are you sure `result` is never null? – aamit915 Oct 05 '12 at 20:11

7 Answers7

5

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.

dennisdrew
  • 4,399
  • 1
  • 25
  • 25
  • thank you. Can it be that you do a .commit() to a sharedPreferences, and next line do a Intent switch, and still, the data is not finished writing? Is there a way to wait until the data is finished being written? – Genadinik Oct 02 '12 at 23:18
  • 1
    No problem. I think what you have just described is probably the root of the issue. I'm not familiar with a way to make sure the preferences file is done being written to. Is there any reason you can't go the Intent extra route I suggested? – dennisdrew Oct 02 '12 at 23:24
2

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.

Alex Zaitsev
  • 2,013
  • 4
  • 30
  • 56
1

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.

Raghav Sood
  • 81,899
  • 22
  • 187
  • 195
  • interesting. I will think about it. – Genadinik Sep 26 '12 at 15:48
  • 2
    But if the "wrong context" would be the answer to the question; wouldn't it mean that all instances of the application would fail - all the time (100% failure)? – dbm Oct 01 '12 at 12:14
  • 1
    This may work, but I don't think it's the best idea to be passing data to a new Activity this way. See my answer using Intents and extras. This causes a read/write every time the new Activity is started. This is not the case with an Intent extra. – dennisdrew Oct 01 '12 at 20:54
  • Right, all these things only fail about less than 5% of the time. So most of the time they work correctly for some reason. – Genadinik Oct 02 '12 at 23:12
  • Since @Genadinik is using getDefaultSharedPreferences() and both the activities belong to the same application, I don't think this is the problem. He could try using a log output that executes at the end of the onPostExecute() method to see if dbm's answer is correct. – Anup Cowkur Oct 03 '12 at 18:33
1

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 :)

Community
  • 1
  • 1
Sean
  • 5,176
  • 2
  • 34
  • 50
1

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

dbm
  • 10,376
  • 6
  • 44
  • 56
  • Regarding _"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)."_, from the question I got the impression that the second activity is **also** started from `onPostExecute`, **after** setting the preference. I could be wrong though. – Joe Oct 02 '12 at 20:05
  • @Joe: Ah, that seems like a fair assumption. Nevertheless, I think my statement still stands solid: Shared preferences shouldn't be written from `AsyncTask`s as you don't know when the background thread will be executed. – dbm Oct 02 '12 at 20:18
0

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.

wseme
  • 805
  • 6
  • 14
0

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.

Raanan
  • 4,777
  • 27
  • 47