Android instrument test wipes the shared preference data while loading native library - android

When I create Android Instrumentation test case below, I find a strange issue. With the line of loading native lib System.loadLibrary("jnidispatch");, the test case is failed; without that line the test case is working fine. I test with any native libraries, the issue is same, so it is not because of the native library behavior to wipe out the shared preference. It seems a bug of android instrumentation test, any one can explain why?
#RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
private static final String MY_ID = "MY_ID";
private static final String TEST_DATA = "Test";
#Test
public void sharedPreferenceTest() {
Context context = InstrumentationRegistry.getContext();
// Set ID
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(MY_ID, TEST_DATA);
editor.apply();
System.loadLibrary("jnidispatch"); // load native libary
// Get ID
SharedPreferences sharedPref1 = PreferenceManager.getDefaultSharedPreferences(context);
String data = sharedPref1.getString(MY_ID, null);
Assert.assertEquals(data, TEST_DATA);
}
}
The sample project can be downloaded here.
UPDATE: In the end I found out it's the device issue, the issue only happens on certain device, like OPPO device.

Related

Mocking SharedPreferences.Editor.putString()

I recently started coding my really first android project using Android Studio 3.1.2 and SDK 19.
I'm writing unit tests for my components at the moment and use Mockito for mocking android API dependant objects. When I wrote the test for my SessionHandler, a helper class, that manages data stored in SharedPreferences I came across the problem, that, if I want to check, if e. g. mockEdit.remove("my_key") was successful, I didn't know, how to mock the behavior in particular.
This is how I prepare my mocking stuff:
private final Context mockedContext = Mockito.mock(Context.class);
private final SharedPreferences mockedPrefs = Mockito.mock(SharedPreferences.class);
private final SharedPreferences.Editor mockEdit = Mockito.mock(SharedPreferences.Editor.class);
private boolean shouldReturnTestUUID = true;
#Before
public void prepareMocks() {
Mockito.when(mockedContext.getSharedPreferences(anyString(), anyInt()).thenReturn(mockedPrefs);
Mockito.when(mockedPrefs.getString("my_key", null)).thenReturn(shouldReturnTestUUID ? "test_UUID" : null);
//this is the one, I got stuck at
Mockito.when(mockEdit.remove("my_key")).thenReturn(mockEdit.putString("my_key", null));
}
The method i'm actually testing:
public synchronized static void removeAppInstanceID(Context context) {
if (appInstanceID != null) {
SharedPreferences sharedPrefs = context.getSharedPreferences("session", Context.MODE_PRIVATE);
sharedPrefs.edit().remove("my_key").apply();
}
}
The test method:
#Test
public void canRemoveUUID() {
shouldReturnTestUUID = false;
SessionHandler.removeAppInstanceID(mockedContext);
assertNull(mockedPreferences.getString("my_key", null));
shouldReturnTestUUID = true;
}
If I try to run this test I get an UnfinishedStubbingException referring to the line where I want to mock mockEdit.remove("my_key"). Seemingly, the stub doesn't know what to do with mockEdit.putString("my_key", null);.
So my question is, how to mock this method, so I can call mockedPrefs.getString("my_key") and check if the returned value is null? Thanks in forward.
You have two options:
Mock SharedPreferences using Robolectric like here:
https://stackoverflow.com/a/9755286/1150795. Robolectric is a common
tool for unit testing Android applications and Mocking objects from
Android SDK
You can add additional layer of abstraction and hide
saving SharedPreferences behind an interface, which can be mocked with Mockito
Maybe a unit test (without integration with Android Framework) would be enough. You could only test that the remove() and apply() methods are called.
For that you need to determine the editor (a mock in this case) returned by edit() method
A Kotlin example, using Mockito, below...
#Test
public void canRemoveUUID() {
// Arrange
val mockEdit = mock(SharedPreferences.Editor::class.java)
`when`(mockEdit.remove("my_key"))
.thenReturn(mockEdit)
val mockedPrefs = mock(SharedPreferences::class.java)
`when`(mockedPrefs.edit())
.thenReturn(mockEdit)
val mockedContext = mock(Context::class.java)
`when`(mockedContext.getSharedPreferences("session", Context.MODE_PRIVATE))
.thenReturn(mockedPrefs)
// Act
SessionHandler.removeAppInstanceID(mockedContext);
// Assert
verify(mockEdit, times(1)).remove("my_key")
verify(mockEdit, times(1)).commit()
}

Trying to mock SharedPreferences using Mockito

I'm trying to use Mockito to test a settings manager which saves data through SharedPreferences.
Since SharedPreferences makes use of Context, I need to use mock classes.
This is my settings manager class:
public class SettingsManager implements ISettingsManager {
protected SharedPreferences prefs;
public SettingsManager(SharedPreferences prefs) {
this.prefs = prefs;
}
private boolean getBooleanPreference(String key) {
return prefs.getBoolean(key, true);
}
private void setBooleanPreference(boolean enabled, String key) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(key, enabled);
editor.commit();
}
}
This is the test case I wrote:
Context mContext = Mockito.mock(Context.class);
SharedPreferences mSharedPreference = Mockito.mock(SharedPreferences.class);
SharedPreferences.Editor mEditor = Mockito.mock(SharedPreferences.Editor.class, Mockito.RETURNS_DEEP_STUBS);
Mockito.when(mSharedPreference.edit()).thenReturn(mEditor);
Mockito.when(mEditor.commit()).thenReturn(true);
Mockito.when(mEditor.putBoolean(Mockito.anyString(), Mockito.anyBoolean())).thenReturn(mEditor);
SettingsManager manager = new SettingsManager(mSharedPreference);
boolean current = manager.areNotificationsEnabled();
manager.setNotificationsEnabled(!current);
boolean newValue = manager.areNotificationsEnabled();
Assert.assertTrue(newValue != current);
The problem is when I set the setNotificationsEnabled flag, the newValue remains the same of current: SharedPreferences does not persist data. How can I save data to SharedPreferences while testing?
Robolectric is an option for this kind of integration test.
Robolectric provides test doubles called "shadows" of common Android classes like Context, SQLiteDatabase and SharedPreferences. The tests you write run in test in your IDE (not androidTest on an emulator or test device) so it is easier to configure tools for test coverage.
The shadow SharedPreference is sandboxed as well so it won't interfere with the actual SharedPreferences on a device.
Convert this to an AndroidTest and use InstrumentationRegistry.getTargetContext() to get a context, this way you can use the class without mocking it

Shared Preferences reset data when app crashed. Please guide

My app got crashed and all the data in shared preference got cleared.
I am saving some flags and maintaining user session in shared preference.
One of the flag is IsFirstLaunch, which tells me whether app is launching for first time or not, if returns true then I am downloading some data from server and storing in SQLite database.
Please guide, thanks in advance.
So after the crash when it went to load the Preferences there was a blank in the preferences xml file which caused the preferences to reset.
To avoid this you could put all preference modifications in synchronized blocks or even use one synchronized static method for all preference writing.
I think - you need a better way of managing and storing the data you're saving.
The next time the shared preferences were accessed however, the xml file was cleared and started new.
for example :
private static final class SharedPreferencesImpl implements SharedPreferences {
...
public String getString(String key, String defValue) {
synchronized (this) {
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
...
public final class EditorImpl implements Editor {
public Editor putString(String key, String value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
...
}
}

Where should I synchronize on this static SharedPreference helper class?

I have created a class holding SharedPreferences access in a static manner. Looking at the AOSP ContextImpl.java's SharedPreferenceImpl, I see that synchronized(this) is used when put and get are executed.
Should I still add synchronized somewhere in my code below?
public class AppPreferences {
// Get static SharedPreferences Editor
private static Editor getEditor(Context ctx) {
return PreferenceManager.getDefaultSharedPreferences(ctx).edit();
}
// Get static SharedPreferences
private static SharedPreferences getPref(Context ctx) {
return PreferenceManager.getDefaultSharedPreferences(ctx);
}
public static String getUserName(Context ctx, String defaul) {
return getPref(ctx).getString("user_name", defaul);
}
public static void setUserName(Context ctx, String text) {
getEditor(ctx).putString("user_name", text).commit();
}
}
In android.app.ContextImpl there is a static field
private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
new HashMap<String, SharedPreferencesImpl>();
(aside private static final HashMap ?! /aside).
This is populated here. So all threads in an application sharing the same context (I have asked here but I am still not quite 100% certain) will share this static map of SharedPreferencesImpl instances - Now whenever you call edit() you get a new EditorImpl instance - so in the "synchronized(this)" you refer to in your question the this refers to the instance of EditorImpl at hand - which does not do much - it just synchronizes access to the internal map of the EditorImpl. But the (different) editors synchronize on the (common) SharedPreferencesImpl instance when they are about to modify this (SharedPreferencesImpl) instance. So in commit() for instance commitToMemory() is called where the synchronization is on SharedPreferencesImpl.this. Keep in mind though that the writes to disk are enqueued in random order (see the javadoc for enqueueDiskWriteSo and notice in commit that no lock is held between writing to memory and enqueing for write to disk). So you should be safe modifying the preferences as long as you do not depend on order of modifications and do not depend on atomically checking and setting a preference value (which needs synchronizing of your own)
NB the code I quote is for 2.3.1_r1 - hopefully still valid

Initialization of static variables in a class of utility functions

For my Android application, I have written a class which is composed of utility functions which are needed at various activites in the application.In this class, I need a context variable(for working with files) and an instance of preference manager and preference editor.Also, a long integer represnting the current date as a timestamp is needed:
private static long today;
private static Context myContext;
private static SharedPreferences sharedPrefs;
private static Editor editor;
Which is correct way to initialize these variables. I have tried doing it via a private constructor as shown below, but I am getting errrors.
private NetworkController()
{
//Getting the Unix timestamp for today
GregorianCalendar aDate = new GregorianCalendar();
GregorianCalendar tDate = new
GregorianCalendar(aDate.get(Calendar.YEAR),aDate.get(Calendar.MONTH),
aDate.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
today = (tDate.getTimeInMillis())/1000;
//The preferences manager for reading in the preferences
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(myContext);
//The preferences editor for modifying the preferences values
editor = sharedPrefs.edit();
}
One approach would be to create an instance of this class in every activity where its used but I don,t want to do that.Any other approach is possible?
If you have a set of things that you use everywhere and only want one instance of, you can use what's called a singleton. For example, here is a very simple one that holds an integer called level:
public class Utility {
private static Utility theInstance;
public int level;
private Utility() {
level = 1;
}
public static getUtility() {
if (theInstance == null) {
theInstance = new Utility();
}
return theInstance;
}
}
Then you can use this like:
Utility u = Utility.getUtility();
u.level++;
However, many people discourage the use of singletons, since they can lead to confusing program behaviour. A good article on this topic is Singletons are Pathological Liars. Singletons can be useful in some situations, but you should be aware of the traps involved in using them.
#Greg is right, just don't use any static stuff for what you want to do. There is no reason you don't want to have normal objects here. Pass the context as parameter and instanciate you objects when you need them to serve you :
private long today;
private Context myContext;
private SharedPreferences sharedPrefs;
private Editor editor;
public NetworkController( Context context )
{
this.context = context;
//Getting the Unix timestamp for today
GregorianCalendar aDate = new GregorianCalendar();
GregorianCalendar tDate = new
GregorianCalendar(aDate.get(Calendar.YEAR),aDate.get(Calendar.MONTH),
aDate.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
today = (tDate.getTimeInMillis())/1000;
//The preferences manager for reading in the preferences
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this.context);
//The preferences editor for modifying the preferences values
editor = sharedPrefs.edit();
}
Singletons are a bad way of programming things, it makes things very hard to test. Even you don't yet use tests, don't use singletons, there lead to very poor quality code and real ball of muds when things get more complicated.
Here you can do this:
public class NetworkController {
SharedPreferences settings;
SharedPreferences.Editor editor;
public NetworkController(Context context){
settings = PreferenceManager.getDefaultSharedPreferences(context);
editor = settings.edit();
}
public void saveName(String name){
editor.putString("name", name).commit();
}
public String getName(){
return settings.getString("name");
}
public static long getTimeStamp(){
return System.currentTimeMillis();
}
}
You can use the class like below:
NetworkController prefs = new NetworkController(context); // Context being an Activity or Application
prefs.saveName("blundell");
System.out.println(prefs.getName()); // Prints 'blundell';
System.out.println(NetworkController.getTimeStamp()); // Prints 1294931209000
If you don't want to create an instance in every class you could create on instance in your Application and always reference that:
public class MyApplication extends Application {
private NetworkController myPrefs;
public NetworkController getPrefs(){
if(myPrefs == null){ // This is called lazy initialization
myPrefs = new NetworkController(this); // This uses the Application as the context, so you don't have issues when Activitys are closed or destroyed
}
return myPrefs;
}
}
You need to add the MyApplication to your manifest:
<application
android:name="com.your.package.MyApplication"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name">
To use this single instance you would do this:
public class MyActivity extends Activity {
public void onCreate(Bundle savedInstanceState){
super(savedInstanceState);
NetworkController prefs = ((NetworkController) getApplicationContext()).getPrefs();
// use this object just like shown above
prefs.saveName("blundell"); // etc
}
}
There's already a bunch of good suggestions posted here, but I suppose another approach for these kind of 'utility'/'helper' functions is to simply pass in the parameters you need the logic to work on. In your case, in stead of trying to make the logic work on a local Context reference, you could simply pass it in:
public static void NetworkController(Context context) {
//Getting the Unix timestamp for today
GregorianCalendar aDate = new GregorianCalendar();
GregorianCalendar tDate = new
GregorianCalendar(aDate.get(Calendar.YEAR),aDate.get(Calendar.MONTH),
aDate.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
long today = (tDate.getTimeInMillis())/1000;
//The preferences editor for modifying the preferences values
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
...
}
The other variables you can calculate/deduce on the fly. It'll probably mean a bit more garbage collection, but should be relatively safe in terms of memory management.

Categories

Resources