7

So, I'm facing this weird problem right now. I HAVE to use SharedPreferences.Editor().commit() in my Android app, but, as the documentation here states,

As SharedPreferences instances are singletons within a process, it's safe to replace any instance of commit() with apply() if you were already ignoring the return value.

You don't need to worry about Android component lifecycles and their interaction with apply() writing to disk. The framework makes sure in-flight disk writes from apply() complete before switching states.

Basically it is safe to replace commit() with apply() if you're not using the return value, BUT, the difference between two as mentioned here, and as warning in Android Studio states, is that commit() immediately writes the data, BUT apply() does that asynchronously.

So my problem here is, I'm changing the language in my app, and I want to restart my app after user chooses the language. But, when user chooses the language, the current chosen language is put in SharedPreferences.

Now, the problem:

Whenever I use apply() instead of commit() and restart my app using the code to restart the app here, the changes are not written on the disk, as, when the app restarts, it does not change the current language, as the value from SharedPreference is not changed, because it is not immediately written on the disk. But, whenever I use commit(), the changes are immediately written, and the language is successfully changed when the app is restarted.

So, the questions:

  1. How can people who wrote the code for commit() and apply() say that it is completely safe to use apply() instead of commit(), if there's very big difference, as commit() writes the data immediately, but apply() does it in background?

  2. If I build my apk, will the commit() be replaced by apply() in code optimization if I'm not using the return value.(I know I can know this by building the release version of the app, but I still won't be sure, because when I use apply(), it veeery frequently 1/10 times actually does change the value from SharedPreference)

A note:

  1. I know how I can use Apply() and still make my app work, maybe I'll have to add some delay before restarting the app? But I'm not sure how it'll work as it'll still take some time to actually write the data on disk, and I don't currently see any method to check if the SharedPreference value was actually changed, so that I can safely restart AFTER the value was changed.
Vedprakash Wagh
  • 3,595
  • 3
  • 12
  • 33

1 Answers1

6

The problem is, that with Runtime.getRuntime().exit(0) or System.exit(0) you'll kill the process and therefore no scheduled async task will execute after.

If you don't intend to change your restart code, you should keep commit instead of apply for this instance and suppress the warning.

  1. It's safe to assume that the statement is valid, because calling exit(0) is an edge-case you should not do normally.
  2. There's no reason to assume that commit will be replaced with apply automatically. If you want to make sure of it, just use the return value.
tynn
  • 38,113
  • 8
  • 108
  • 143
  • Android studio gives a big warning saying it's safe to replace commit with apply, so maybe it'll replace it? Also, how do I exit my app without using System.exit(0)? Because I'm using the second method given there, not the first one that's mentioned. – Vedprakash Wagh Jun 24 '19 at 14:45
  • 1
    Android Studio doesn't expect the process to be killed with `exit(0)`. That's maybe the only case where it's not safe to replace `commit` with `apply`. – tynn Jun 24 '19 at 14:56
  • 1
    Exactly what @tynn says, the Android Studio warning is just that, a WARNING, to raise a potential problem; in this particular case, you're performing an exceptional operation (calling the OS System.exit method) which will terminate the process, this is not something the shared preferences apply method is expecting to happen under normal circumstances, so yes, suppressing the Warning is ok; I'd leave a comment explaining it to the next poor soul who has to read that code. – Martin Marconcini Jun 24 '19 at 14:59
  • yeah maybe that's the thing. You're correct about that though. But I thought when they say it's completely safe to replace commit() with apply(), then there must be some queue of tasks to be written on disk, which is independent of the app itself? @MartinMarconcini read this comment,I mean there must be something like I said if they're saying that right? – Vedprakash Wagh Jun 24 '19 at 15:02
  • Not really. It's still in the process itself. But to put it in perspective: It's not safe to call `exit(0)` and you should not do it if possible. – tynn Jun 24 '19 at 15:02
  • 1
    Yeah you're right about that, but then there's problem of restarting the app. How would I restart the app completely after changing the language without using exit(0)? – Vedprakash Wagh Jun 24 '19 at 15:04
  • I doubt that's possible, that's why it's totally fine to compromise and use `commit` in this case. – tynn Jun 24 '19 at 15:05
  • Yeah I solved my problem by using if loop which will restart the app if commit returns true. – Vedprakash Wagh Jun 24 '19 at 15:07
  • Let's put it this way, calling exit(0) is a _hack_ in terms of what the Android framework can handle for you. It's like an app can have all the error logic code you can imagine, but if you pull the battery, it won't have much to do if the hardware doesn't help, so... I recommend you take a look [at the source code of shared preferences](https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/app/SharedPreferencesImpl.java) to determine if/what is safe; neither `apply()` nor `commit()` are guaranteed to write *immediately*, so keep an eye on this and adapt. :) – Martin Marconcini Jun 24 '19 at 15:17
  • Yeah I mean, I don't think there's much I can do here. Except maybe there should've been some listener which would've listened if the data was written, and if it was, then execute some code afterward. Also, it's not that exceptional case according to me, as, developers sometimes might want to restart the app after making changes in SharedPreferences/Settings, and I don't see any way to restart the app without using exit(0) @MartinMarconcini – Vedprakash Wagh Jun 24 '19 at 15:28
  • It depends what you mean by "restart" the app. There are ways to restart the activity task/relaunch the intents, but that is not the same as killing a linux process (which is what the System.exit call is doing) and starting it again. For example, if you have a static field, restarting activities and the task stack, will not reset said values (because they are tied to the actual Process). There's no "official" Android way (that I know of) for telling an App: Hey, restart again in a new process and kill the old one. Essentially, you're doing this the only way I know it's possible. – Martin Marconcini Jun 24 '19 at 15:31
  • 1
    ... but, as you are finding out, there are side-effects and consequences for doing so. Aka: things the framework didn't expect and was not designed/programmed to deal with. :) – Martin Marconcini Jun 24 '19 at 15:33
  • haha, yeah, @MartinMarconcini, basically gotta work with what we got! – Vedprakash Wagh Jun 24 '19 at 15:35