I am able to call Retrofit and SharedPreferences and Intent fine from an Activity and all works. But it gets messy real fast and lots of repetition thus I want to call them from a helper class instead. It makes it complicated cos I end up having to extend the Helper class with the Application class, followed by IDE complaining I have missing XML files which are actually not needed for a helper class.
Feel like I am over complicating things. What's the solution here? Write directly on Activity classes and endure the code repetitions or is there an elegant solution to this?
This is how I tried to extract the logic for Retrofit and Intent away from an Activity class Using a helper.
public class RetrofitHelper extends Application {
private final String ERROR_MSG = "username is invalid";
private static final String TAG = "RetrofitHelper";
private static final String API_KEY = "";
private VerificationHelper verificationHelper;
public RetrofitHelper(VerificationHelper verificationHelper) {
this.verificationHelper = verificationHelper;
}
public Retrofit getRetrofit(String baseUrl){
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(setTimeout())
.addConverterFactory(GsonConverterFactory.create());
return builder.build();
}
public void performCallBack(Call<User> call){
call.enqueue(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
String usernameToken = Objects.requireNonNull(response.body()).getUsername();
String username = null;
if(usernameToken != null){
username = verificationHelper.parseToken(usernameToken, API_KEY);
}
if (username != null && !username.equals(ERROR_MSG)){
Log.i(TAG, "onResponse: Success " + username);
// verificationHelper.setPreferences(username);
verificationHelper.goToMainActivity();
}
else {
Log.i(TAG, "onResponse: Incorrect response.");
}
}
#Override
public void onFailure(Call<User> call, Throwable t) {
Log.i(TAG, "onFailure: server error");
}
});
}
private OkHttpClient setTimeout(){
return new OkHttpClient().newBuilder()
.connectTimeout(60, TimeUnit.MINUTES)
.readTimeout(60, TimeUnit.MINUTES)
.writeTimeout(60, TimeUnit.MINUTES)
.build();
}
}
For reference, VerificationHelper called from the above Helper.
public class VerificationHelper extends Application {
private static boolean isValidCredential = false;
public static boolean isValidCredential() {
return isValidCredential;
}
static void setValidCredential(boolean validCredential) {
isValidCredential = validCredential;
}
private <T> String convertObjToJson(T object){
// some logic
return "";
}
public String createToken(String apiKey, User user, long ttlMillis) {
//some logic
return "";
}
String parseToken(String token, String apiKey) {
//some logic
return "";
}
//trying to keep this here so I call at many places.
void setPreferences(String username){
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("username", username);
editor.putBoolean("isLoggedIn", true);
editor.apply();
}
//trying to keep this here so I call at many places.
void goToMainActivity(){
Intent mainIntent = new Intent(this, MainActivity.class);
startActivity(mainIntent);
}
}
Make a Helper Class (no Activity extended) and pas the context to the functino that you want to call. An Exmaple with shared preferences:
public class SharedPreferenceHelper {
private final static String PREF_FILE = "<Shared_PReferences_Name>";
/**
* Set a string shared preference
* #param context - Context (Activity that is calling the function)
* #param key - Key to set shared preference
* #param value - Value for the key
*/
public static void setSharedPreferenceString(Context context, String key, String value){
SharedPreferences settings = context.getSharedPreferences(PREF_FILE, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putString(key, value);
editor.apply();
}
}
The you can call the fucntino like this:
SharedPreferenceHelper.setSharedPreferenceString(MainActivity.this, <key>, <value>);
Source: https://github.com/nickescobedo/Android-Shared-Preferences-Helper/blob/master/SharedPreferenceHelper.java
From the documentation you can see getDefaultSharedPreferences method takes a context as a parameter. This context can be an Activity, a Fragment or a Service. The simplest solution would be to pass the context to your helper class methods via dependency injection. Something like:
void setUsernamePref(String username, Context context){
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("username", username);
editor.putBoolean("isLoggedIn", true);
editor.apply();
}
Remember to avoid storing context statically in the helper class, otherwise you'll risk to fall into unwanted memory leaks
Related
I am trying to observe data changes in shared preferences. I found this similar question answered by #SimplyProgrammerand followed the steps that he directed and at the end of the day my observer was still not working.
Then I decided to seek some assistance to better understand why.
this is my implementation
I started by implementing the abstract live data
SharedPreferences preference;
String key;
T defValue;
public SharedPrefferenceLiveData(SharedPreferences preference,String key,T defValue){
this.preference=preference;
this.key=key;
this.defValue=defValue;
}
private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener=new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(SharedPrefferenceLiveData.this.key.equals(key)){
setValue(getValueFromPreferences(key,defValue));
}
}
};
abstract T getValueFromPreferences(String key, T defValue);
#Override
protected void onActive(){
super.onActive();
setValue(getValueFromPreferences(key,defValue));
preference.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
}
#Override
protected void onInactive() {
preference.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener);
super.onInactive();
}
}
I then implemented the live data type class
public class LocationLiveData extends SharedPrefferenceLiveData<String>{
public LocationLiveData(SharedPreferences preferences, String key, String string){
super(preferences,key,string);
}
#Override
public String getValueFromPreferences(String key, String defValue) {
return preference.getString(key,defValue);
}
}
I then added this to my Preference management class like so
instantiation and setting getters
private LocationLiveData sharedPreferenceLiveData;
public LocationLiveData getSharedPrefs(){
return sharedPreferenceLiveData;
}
then assigned values like so
public void saveUserLocation(Location location){
...
settings = context.getSharedPreferences(MyPREFERENCES, Context.MODE_PRIVATE);
editor = settings.edit();
editor.putString(User_Location, currentLocation);
editor.apply();
sharedPreferenceLiveData=new LocationLiveData(settings,User_Location,currentLocation);
}
then in my activity, I access sharedPreferenceLiveData like this
#Inject
SharedPreference sharedPreference;
...
...
LocationLiveData liveData;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
...
...
liveData=sharedPreference.getSharedPrefs();
...
...
observeMarkerLocation();
}
...
...
...
//the observer
private void observeMarkerLocation() {
if(liveData!=null){
liveData.observe(this,locationString->{
if(locationString!=null){
if(!sharedPreference.getBoolValue(SharedPreference.IS_FOLLOWING_ALERT)){
Gson gson=new Gson();
Type type=new TypeToken<Location>(){}.getType();
Location userLocation=gson.fromJson(locationString,type);
currentLocation=userLocation;
}else{
Gson gson=new Gson();
Type type=new TypeToken<VictimFollowingResponse>(){}.getType();
VictimFollowingResponse victimFollowingResponse=gson.fromJson(locationString,type);
List<Point> points=victimFollowingResponse.getPoints();
List<LatLng> latLngPoints=new ArrayList<>();
for(Point point:victimFollowingResponse.getPoints()){
latLngPoints.add(new LatLng(point.getLat(),point.getLong()));
}
int pointListSize=points.size();
if(pointListSize>0){
victimUser.setLatitude(points.get(pointListSize-1).getLat());
victimUser.setLongitude(points.get(pointListSize-1).getLong());
}
drawPolyLIne(latLngPoints);
}
}
});
}
}
yeah thats it .
in this case, the live data keeps returning null in the activity even after being set in a service.
I think that your code is too complicated. You can simply listen for SharedPreferences changes, using registerOnSharedPreferenceChangeListener() listener, when your app is turned on.
public class MyApplication extends Application {
private static MyApplication singleton;
public SharedPreferences preferences;
public static MyApplication getInstance() {
return singleton;
}
#Override
public void onCreate() {
super.onCreate();
singleton = this;
preferences = PreferenceManager.getDefaultSharedPreferences(this);
preferences.registerOnSharedPreferenceChangeListener(listener);
}
private SharedPreferences.OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
//do your code here
}
};
public void unregisterListener() {
preferences.unregisterOnSharedPreferenceChangeListener(listener);
}
}
It overrides an application class, but you can also use this in any activity, but like docs say
Caution: The preference manager does not currently store a strong reference to the listener. You must store a strong reference to the listener, or it will be susceptible to garbage collection. We recommend you keep a reference to the listener in the instance data of an object that will exist as long as you need the listener.
In my example, you should also override an application tag in manifest file
<application
android:name=".MyApplication"
>
</application>
And remember to unregister listener in any activity, before you'll exit an app
#Override
protected void onPause() {
MyApplication.getInstance().unregisterListener();
super.onPause();
}
If you have your own preference file, just change
preferences = PreferenceManager.getDefaultSharedPreferences(this);
to
preferences = getSharedPreferences("pref_filename", MODE_PRIVATE);
To access that preferences, you no need any reference to it. You can get instance of sharedpreferences file in any activity you want. That will be work, because interface will listen for any changes in preferences file. Note that, if you'll pass a key with the same value which is already contained, listener doesn't recognize that as change. So, you can change int value eg. from 2 to 3, and it will work, but from 2 to 2 won't work.
I want to fetch BASE_URL from SharedPreferences inside my RetrofitClient Class.
My Code Is:
RetrofitClient.java:
public class RetrofitClient {
private static final String BASE_URL = getBaseUrl();
private String getBaseUrl() {
SharedPreferences sp1 = getSharedPreferences("Login", MODE_PRIVATE);
String apiUrl = sp1.getString("apiUrl", null);
return apiUrl;
}
private RetrofitClient() {
//MyRetrofitClient...
}
}
How can i get get it work?
MainActivity.java:
Call<LoginResponse> call = RetrofitClient
.getInstance()
.getApi()
.loginUser(username, password, action);
call.enqueue(new Callback<LoginResponse>() {
}
For passing data , you need to make a constructor for that class . something like this
public class ApiClient {
Context my_conext;
public ApiClient (Context context) {
my_conext= context;
}
SharedPreferences sp1 = my_context.getSharedPreferences("Login", MODE_PRIVATE);
}
EDIT: from your updated code .
you are doing it wrong .
first of all don't call function at the time of initialisation. use like this
retrofit = new Retrofit.Builder()
.baseUrl(getBaseUrl(my_context))
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
Second thing . you need **Constructor** constructor has the same name like class . carefully see my above answer . both has same name ApiClient. so in your case
public class RetrofitClient {
Context my_conext;
public RetrofitClient (Context context) {
my_conext= context;
}
}
from activity , you are calling this RetrofitClient , call like this
RetrofitClient(MainActivity.this).getApi();
I have an application which manages data through SharedPreferences (SP).
I splitted them in different XML files to make the operations faster (clear, commit, apply, ...) and be more organized.
I also created a class for each one to abstract operations and create boilerplate code to avoid duplicating the same code on classes that need to update data on SP.
Since the idea of having a SP is to share information throughout the application, in my case it's better to use the application context to manipulate data on it, even though SP is a single instance.
But I'm not sure if it is a good practice to do this. Have you ever faced a similar situation?
My current implementation (snippet):
MyApplication.java
public class MyApplication extends Application {
private static PreferenceHelper mPref;
#Override
public void onCreate() {
mPref = new PreferenceHelper(this);
}
}
PreferenceHelper.java
public class PreferenceHelper {
private PreferenceUser mPrefUser;
private PreferenceTechnician mPrefTechnician;
public PreferenceHelper(Context context) {
this.mPrefUser = new PreferenceUser(context);
this.mPrefTechnician = new PreferenceTechnician(context);
}
public PreferenceUser getPrefUser() {
return mPrefUser;
}
public PreferenceTechnician getPrefTechnician() {
return mPrefTechnician;
}
}
PreferenceUser.java
public class PreferenceUser {
private static final String PREF_USER_FILENAME = "user";
private SharedPreferences mPref;
public PreferenceUser(Context context) {
mPref = context.getSharedPreferences(PREF_USER_FILENAME, Context.MODE_PRIVATE);
}
public void clear() {
mPref.edit().clear().commit();
}
public void saveNewUser(User user) {
SharedPreferences.Editor editor = mPref.edit();
editor.putString(user.getId(), gson.toJson(user));
editor.commit();
}
public List<User> getAllList() {
...
// get a map, convert to POJO using Gson,etc
...
}
}
PreferenceTechnician.java
Same as PreferenceUser, but for Techs
MainActivity.java (or any other that needs to read/write from/to SP)
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
writeSomethingOnSP();
}
private void writeSomethingOnSP() {
MyApplication.getPref().getPrefUser().saveNewUser(new User(1, "Ana", "Developer"));
}
}
Whenever I need to read or write something on the SharedPreference, I use MyApplication.getPref().getPrefNameOfPreference().doSomeAction();.
Can I have your opinion about this approach?
I had similiar issues and I dediced to fix with static methods.
Since SharedPreferences are not heavly used, I decided to not store any instance of them but to get them whenever I need them. Also, since all read/write operations are centered in the same class, I have the control that I need (save this setting on that file etc)
I did something like:
public class SettingsUtil {
private static final String PREF_USER_FILENAME = "user";
private static final String PREF_TECH_FILENAME = "tech";
public static void saveNewUser(Context context, User user) {
SharedPreferences sharedPref = context.getApplicationContext().getSharedPreferences(PREF_USER_FILENAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
....
editor.apply();
}
public static void addNewTech(Context context, Tech tech) {
SharedPreferences sharedPref = context.getApplicationContext().getSharedPreferences(PREF_TECH_FILENAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
....
editor.apply();
}
...
}
Then, to call:
Settings.Util.addNewUser(this, new User(1, "Ana", "Developer"));
Not sure if this helps you. I found it simple and it gave the control that I was looking for.
What I would like to know is if there is a way to update multiple activities from one activity.
For example from my activityC (in my case EditProfileActivity) I update user profile. activityC is a child of activityB, which is a child of activityA, and open from mainActivity's navdrawer. I also have activityE that can be opened from activityC and activityF that can be open from activityA. I want to update all of them.
When I change a user name, photo, about or anything else, I want to send that changed information even without closing activityC. And if I go back or forward from it, it should have updated user information already. If user did not change anything it should not update those activities.
If it was only one activity I would do it via intent or onActivityResult.
Is there a way to do this without shared preferences, or startActivityForResult and onActivityResult?
you can use event bus for this purpose like this explained here:
you can create event like this:
public static class UpdateEvent { /* your getter and setter */ }
Prepare subscribers: Declare and annotate your subscribing method, optionally specify a thread mode:
#Subscribe(threadMode = ThreadMode.MAIN)
public void onUpdateEvent(UpdateEvent event) {
/* event fire here when you post event from other class or fragment */
};
Register - unregister your subscriber.
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);//Register
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);//unregister
}
Fire events like this:
EventBus.getDefault().post(new UpdateEvent());
What you can also use, and in my opinion is easier and better than EventBus, is to use a LocalBroadcastManager. It will allow you send broadcasts across your application which can be received by one or more activities. I guess it's pretty much what you want.
Documentation:
https://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html
Simple solution is Broadcast Reciever
Try this
In your other activity class declare this
BroadcastReceiver broadCastNewMessage = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// do your stuff here
}
};
Now in onCreate() register this
registerReceiver(this.broadCastNewMessage, new IntentFilter("bcNewMessage"));
And in onDestroy()
unregisterReceiver(broadCastNewMessage);
Now Call this method from the activity where u want to update other activity
sendBroadcast(new Intent().setAction("bcNewMessage"));
You can set information in sharedprefences and can get fetch information in any activity
I have created this Class for saving object in sharedprefrences
public class DroidPrefs
{
public static boolean contains(Context context, String key) {
return getDefaultInstance(context).contains(key);
}
public static <T> T get(Context context, String key, Class<T> cls) {
return getDefaultInstance(context).get(key, cls);
}
public static void apply(Context context, String key, Object value) {
getDefaultInstance(context).apply(key, value);
}
public static void commit(Context context, String key, Object value) {
getDefaultInstance(context).commit(key, value);
}
public static DroidPrefs getDefaultInstance(Context context) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
return new DroidPrefs(sp);
}
private Gson mGson;
private SharedPreferences mSharedPrefs;
public DroidPrefs(SharedPreferences sharedPrefs)
{
mGson = new Gson();
mSharedPrefs = sharedPrefs;
}
public boolean contains(String key) {
return mSharedPrefs.contains(key);
}
public <T> T get(String key, Class<T> cls) {
if (contains(key)) {
return mGson.fromJson(mSharedPrefs.getString(key, null), cls);
}
try {
return cls.newInstance();
}
catch (Exception e) {
throw new IllegalArgumentException("class must have an empty constructor");
}
}
public void apply(String key, Object value) {
put(key, value).apply();
}
public void commit(String key, Object value) {
put(key, value).commit();
}
#SuppressLint("CommitPrefEdits")
private SharedPreferences.Editor put(String key, Object value) {
SharedPreferences.Editor e = mSharedPrefs.edit();
e.putString(key, mGson.toJson(value));
return e;
}
public void clear(){
SharedPreferences.Editor e = mSharedPrefs.edit();
e.clear().apply();
}
}
You can save value to any key by using apply or commit method
DroidPrefs.apply(context, "your key", yourobject);
You can fetch value by using get method
yourobject= DroidPrefs.get(this,"your key",yourclass.class);
I have a class Settings with static methods which I use to save and load my data. This is for example the Save() method:
public static void Save(SETTING_KEY key, String value)
{
SharedPreferences sp = _context.getSharedPreferences(prefName, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString(key.toString(), value);
editor.commit();
}
Now I'd like to make the class Settings observable. The problem is that extending my Settings-class from Observable does not work, as I don't instantiate this class as I only use its static methods.
Is there any easy way to make this static class observable?
Add your own observer mechanism. Simple implementation below
public interface Observer {
void onSettingsChanged(String key, String value);
}
private static Observer observer;
public static void setObserver(Observer observer_) {
observer = observer_;
}
public static void Save(SETTING_KEY key, String value) {
SharedPreferences sp = _context.getSharedPreferences(prefName, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString(key.toString(), value);
editor.commit();
if (observer != null) {
observer.onSettingsChanged(key, value);
}
}
You could also use a List<Observer> if you need more than 1 and you can also use some sort of event bus (e.g. Otto) system to publish events without having to provide a direct observer mechanism.
You could try using a Singleton pattern? (Java Singleton Pattern)
That way, you can extend observable.
You could combine a singleton with static methods.
class Settings extends Observable {
private static Settings instance;
private static synchronized Settings getInstance() {
if(instance == null) {
instance = new Settings();
}
return instance;
}
public static void Save(SETTING_KEY key, String value)
{
SharedPreferences sp = _context.getSharedPreferences(prefName, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString(key.toString(), value);
editor.commit();
getInstance().notifyObservers(value); // or whatever
}
// can't override addObserver with a static method, so make a new method instead
public static void addObserverStatic(Observer observer) {
getInstance().addObserver(observer);
}
}
As others have said, static methods on a singleton is a bit naughty. Do it some other way if you can.