I've been playing around with Transfuse (http://androidtransfuse.org/) and am now trying to tackle SharedPreferences. The documentation makes this seem pretty straight forward:
#Activity
public class Example{
#Inject #Preference(value = "favorite_color", default = "green")
String favColor;
}
However, as I understand it, SharedPreferences are retrieved by name, not just by key. So, how does Transfuse know the name of the SharedPreferences file I'm trying to access?
I've tried something like this to no avail:
#Activity
public class MainActivity{
public static final String PREF_NAME = "pref_name";
#Inject
android.app.Activity mActivity;
#Inject #Preference(value = PREF_NAME, defaultValue = "")
String mPreference;
#Inject #View(R.id.preference)
EditText mPreferenceEditText;
#RegisterListener(R.id.button_2)
android.view.View.OnClickListener mSavePrefListener = new android.view.View.OnClickListener() {
#Override
public void onClick(android.view.View v) {
String val = mPreferenceEditText.getText().toString();
mActivity.getSharedPreferences("the_shared_prefs", Context.MODE_PRIVATE)
.edit()
.putString(PREF_NAME, val)
.apply();
}
};
#OnResume
private void displayPrefText(){
mPreferenceEditText.setText(mPreference);
}
}
Thanks for your help!
The #Preference injection uses the PreferenceManager.getDefaultSharedPreferences() method (as CommonsWare suggested) to look up the SharedPreferences object. This is a convenience for using the default preferences directly. Your example would basically generate the following injection:
delegate.favColor = activity.getDefaultSharedPreferences()
.getString("favorite_color", "green");
If you like, you can set up Transfuse to inject a specific SharedPreferences object via a Provider or #Provides method and qualifier:
#TransfuseModule
class Module{
#Provides #Named("the_shared_prefs")
public SharedPreferences build(Activity activity){
return activity.getSharedPreferences("the_shared_prefs", Context.MODE_PRIVATE)
}
}
Which you could then inject into your activity like so:
#Activity
public class MainActivity{
public static final String PREF_NAME = "pref_name";
#Inject #Named("the_shared_prefs")
SharedPreferences theSharedPreferences;
#Inject #View(R.id.preference)
EditText mPreferenceEditText;
#RegisterListener(R.id.button_2)
android.view.View.OnClickListener mSavePrefListener = new android.view.View.OnClickListener() {
#Override
public void onClick(android.view.View v) {
String val = mPreferenceEditText.getText().toString();
theSharedPreferences
.edit()
.putString(PREF_NAME, val)
.apply();
}
};
}
We may want to expand the #Preference injection to allow you to specify a non-default shared preferences. Would this help?
Related
I am new in Android unit testing and I want to add some unit tests in an existing project. I am using the MVP design architecture. Inside my presenter, I have a call to PreferenceManager in order to get the default SharedPrefences but it always returns null. I have followed some tutorials and advices across stackoverflow on how to mock PreferenceManager and SharedPreferences but I can't make it work. This is my presenter class
#RunWith(MockitoJUnitRunner.class)
public class SettingsPresenterTest {
#Mock
private SettingsView mView;
#Mock
private LocalConfiguration conf;
private SettingsPresenter mPresenter;
public SettingsPresenterTest() {
super();
}
#Before
public void startUp() throws Exception {
MockitoAnnotations.initMocks(LocalConfiguration.class);
MockitoAnnotations.initMocks(PreferenceManager.class);
mPresenter = new SettingsPresenter(mView);
final SharedPreferences sharedPrefs =
Mockito.mock(SharedPreferences.class);
final Context context = Mockito.mock(Context.class);
Mockito.when(PreferenceManager.getDefaultSharedPreferences(context)).
thenReturn(sharedPrefs);
}
#Test
public void notificationsEnabledClicked() throws Exception {
boolean notifsEnabled = false;
mPresenter.notificationsEnabledClicked(notifsEnabled);
Mockito.verify(mView).setNotificationsView(notifsEnabled);
}
}
and here is the method where the SharedPreferences are returned null
public class LocalConfiguration {
public TerritoryDto getLastSavedTerritory() {
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(H.getContext());
String terrritoryString = preferences.getString(SAVED_TERRITORY,
null);
return
SerializerHelper.getInstance().deserialize(terrritoryString,
TerritoryDto.class);
}
}
Could you give me some guidelines on how to resolve this error?
Instead of directly referring to Android SDK, abstract that out from your presenter logics. What this means is, that instead of performing PreferenceManager.getDefaultSharedPreferences(), create an abstraction and ask for territoryString from your abstraction.
What will this give to you, is that your presenter won't know about the precense of neither PreferenceManager nor SharedPreferences, which are from Android SDK, thus you would have enough seams to perform pure unit testing.
Having said this, let's implement the abstractions. Having declared following interface:
public interface Storage {
#Nullable
String getSavedTerritory();
}
To which the concrete implementation would be:
public SharedPrefsStorage implements Storage {
private final SharedPreferences prefs;
public SharedPrefsStorage(Context context) {
prefs = PreferenceManager.getDefaultSharedPreferences();
}
#Nullable
#Override
public String getSavedTerritory() {
return prefs.getString(SAVED_TERRITORY, null);
}
}
Then your presenter would become something like this:
public class LocalConfiguration {
final Storage storage;
public LocalConfiguration(Storage storage) {
this.storage = storage;
}
public TerritoryDto getLastSavedTerritory() {
final String territory = storage.getSavedTerritory();
return SerializerHelper.getInstance().deserialize(territory, TerritoryDto.class);
}
}
This would give you a seam to perform pure unit testing:
#Test
void someTest() {
when(storage.getSavedTerritory()).thenReturn("New York");
...
}
No need to worry about mocking PreferenceManager anymore.
I have just one class where I need to access SharedPreferences:
public class MyUtils {
public static String packageMe(Object input){
// do stuff here
// need SharedPreferences here
}
public static Object unpackageMe(String input){
// do stuff here
// need SharedPreferences here
}
}
I tried this:
public class MyUtils extends Activity
But, as you know, I cannot access SharedPreferences from a static method.
I thought about passing in the context to the static methods, but that extends the number of classes out to four that I will need to modify, and the classes are already extending AsyncTask:
public class SomeClass01 extends AsyncTask {
#Override
protected Object doInBackground(Object[] params){
MyUtils.packageMe(abc_123_object);
// do stuff here
}
}
So, I thought that maybe I could pass the context into those four classes. However, there are a couple dozen classes that I would need to modify that use those four classes, that in turn use that single class.
public class SomeTopClass extends FragmentActivity implements x, y, z {
new SomeClass01.execute(abc_123_object);
// do stuff here
}
I don't know if I want to be passing a context reference that deep into my code.
I saw here on StackOverflow about putting a reference to the SharedPreferences in my abc_123_object model, but there are quite a few objects I use (other than abc_123_object) and I don't want to have to jerry-rig so many classes.
So, is there a way for me to do this without modifying dozens of classes and passing context references all around my code, or am I stuck?
Thanks
Create static variable in your Application class.
public class MyApp extends Application{
public static Context context;
#Override
public void onCreate() {
context = this;
}
}
Then use it when you need.
public static String packageMe(Object input){
// do stuff here
// need SharedPreferences here
// context = MyApp.context
}
As Dusan mentioned, using an application class is an easy way to do this:
In your application class:
private static MyApplication sInstance = null;
private SharedPreferences mPrefs
public static MyApplication getApp()
{
return sInstance;
}
public SharedPreferences getSharePreferences()
{
return mPrefs;
}
in onCreate():
sInstance = this;
mPrefs = getSharedPreferences(PREF_FILE, MODE_PRIVATE);
Then in your code simply do:
MyApplication.getApp().getSharePreferences();
Your Application's onCreate() is guaranteed to be executed before any activity is created, so unless you are doing something really weird, it should be safe.
I tried to create custom class to fetch some values from SharedPreferences.
My aim is to reach to that values from any class.
I am getting null Pointer exception on
SharedPreferences prefs = getApplicationContext().getSharedPreferences("UserFile", MODE_PRIVATE);
My code is as below;
public class UserInfo extends Application {
private String token;
private String SAVED_USERNAME;
public UserInfo() {
SharedPreferences prefs = getApplicationContext().getSharedPreferences("UserFile", MODE_PRIVATE);
token = prefs.getString("Token", null);
}
public String getToken() {
return token;
}
}
What might be the wrong?
Usually Android components are initialized during their lifecycle. In this particular case you can't access application Context and SharedPreferences because they're not initialized yet.
Second problem might be (thanks to my crystall ball) that you did not added your Application to AndroidManifest
So, your first thought might be to move initialization code from constructor to onCreate. This would solve this particular problem.
However, it's a bad practice to do what you're doing. Because there can be only 1 Application component per application. This will limit you to 1 such singleton per app. Consider using Application to provide application Context as singleton and create another singleton for providing UserInfo.
No examples, please exercise yourself.
Just have this method in a util class. No need to extend application.
public static String getToken(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getString("Token", null);
}
There is a rule in android - don't use constructor of app component: Activity/Fragment/Application/Service... there is onCreate() method, because in your constructor context will be null. So move your code to onCreate(). Also you need set your UserInfo as application in Manifest.
You don't create constructor of Application class instead, use the code in onCreate():
#Override
public void onCreate() {
super.onCreate();
SharedPreferences prefs = getApplicationContext().getSharedPreferences("UserFile", MODE_PRIVATE);
token = prefs.getString("Token", null);
}
and use it from any activity:
UserInfo userInfo = (UserInfo)getApplication();
String token = userInfo.getToken();
public class MyApp extends Application {
private static MyApp _instance;
#Override
public void onCreate() {
super.onCreate();
_instance = this;
}
public static MyApp getInstance(){
return _instance;
}
public String getToken() {
return getSharedPreferences("UserFile", MODE_PRIVATE).getString("Token", null);
}
}
In your manifest:
<application
android:name="your.package.MyApp"
>
If you whant use :
String token = MyApp.getInstance().getToken();
Make sure you have registered this class in your AndroidManifest.XML file.
<application android:name=".UserInfo"
...
/>
Note: Your way for accessing shared preferences does not seem good. I rather myself declare a class named PreferencesHelper and put all preferences stuff there.
public class PreferencesHelper{
private SharedPreferences mPrefs;
public PreferencesHelper(Context context){
this.mPrefs = context.getSharedPreferences("name", Context.MODE_PRIVATE);
}
public getToken() {
return mPrefs.getString("Token", null);
}
public String setToken(String token) {
mPrefs.edit().putString("Token", token).apply();
}
}
Somehow, I can't get this done. This is all what I tried till now:
Main:
private String myState;
public String getState() {
return myState;
}
public void setState(String s) {
myState = s;
}
Async:
Main appState = ((Main)getApplicationContext());
String state = appState.getState();
Error: No enclosing instance of the type Main is accessible in scope
Tried with Helper(Globals) class.
public class Globals extends Application{
private String test= "1";
}
Main:
private Globals mGlobals;
mGlobals = new Globals();
mGlobals.test = "2";
//Do Async thing
Async:
private Globals mGlobals;
mGlobals = new Globals();
print mGlobals.test;
// (result is 1, should be 2)
Also something else, but don't remember good.
Tried alot of things (backspace and del buttons are over-used :p )
But I can't get everything working.
Async class doesn't have an activity.
Code pasted: http://pastebin.com/ikcsdL1p
Declare a constructor in your async class like
public class Task extends AsyncTask<Void,Void,Void>
{
private Context context;
public Task(Context context)
{
this.context=context;
}
}
And from your main class
new Task(this).execute((Void)null);
Instead of trying to get Application Context , try to pass Context directly in AsyncTask Constructor. as #Mohd Mufiz already mentioned in his Answer.
Try to use SharedPreferences... To create and call String var use ...
String button_name_11 = getSharedPreferences("PREFERENCE", MODE_PRIVATE).getString("butname11", "EMPTY");
And then to change, just ...
getSharedPreferences("PREFERENCE", MODE_PRIVATE)
.edit()
.putString("butname11", "NEW_STRING" )
.commit();
I need to stock some datas in my application.
I know that i can do it like this:
class:
public class MyApplication extends Application {
private String someVariable;
public String getSomeVariable() {
return someVariable;
}
public void setSomeVariable(String someVariable) {
this.someVariable = someVariable;
}
}
Implementation:
MyApp appState = ((MyApp)getApplicationContext());
String state = appState.getSomeVariable();
This is working if i'm in an activity.
But if i'm in a class not extended from Activity, how can I access at my datas?
thanks in advance for your help!
You can use a Singleton design pattern. You can then use it anywhere, because it has static access.
public class SingletonClass {
private static SingletonClass _instance = null;
private int _value = 0;
private SingletonClass() {
}
public static SingletonClass getInstance() {
if (_instance == null)
_instance = new SingletonClass();
return _instance;
}
public int getValue() {
return _value;
}
public void setValue(int value) {
_value = value;
}
}
and then access it like this:
SingletonClass.getInstance().getValue();
Note: This is a good and easy workaround for some programming problems, but use it very wisely.. it comes with it's problems
Use SharedPrefrences
http://developer.android.com/guide/topics/data/data-storage.html
Perhaps by injecting all the required for a class data via constructor or special setter, I would suggest former one. (Constructor Injection vs. Setter Injection)
There are more solutions like static fields but personally I do not like this approach since statics sometimes makes unit testing a bit messy.
BTW, what kind of variables you want to share?
I use, it may be gruesome to some, a class with static variables, that you can retrieve from every class in the app.
Just create a class with all the field as static, and you can use them throughout your app. It doesn't get erased, only when stopping the app.
You could also just add static variables to your application class.
You can use static methods (or variables if they are public). It's really a little messy, but if you group them (methods) in the right way you'll earn happinnes and satisfaction )
static public int getSomeInt(){
//something
}
And then anywhere in your app use
int x=MyApplication.getSomeInt();
By the way, using this style, you don't need to extend Application class. It's better to create an abstract class for such purposes.
Pass the context of your activity as a param to the method or class:
// ...
public void doStuff(Context context) {
// for example, to retrieve and EditText
EditText et = context.findViewById(R.id.editText1);
}
then, on your activity, you would do:
// ...
MyClass myClass = new MyClass();
// ...
myClass.doStuff(this);
// ...