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);
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've created an Asynctask in an activity and now i want to return the variable "realimage" to that Asynctask but i cant seem to access it...
public class PhotoUtils
{
public static Photo getImage(String id)
{
unsplash.getPhoto(id, new Unsplash.OnPhotoLoadedListener()
{
#Override
public void onComplete(Photo photo)
{
Photo realImage=photo;
}
#Override
public void onError(String error)
{
}
});
return realImage; //This line shows error that cannot resolve symbol
realImage
}
}
This is my Async Task which is in the other activity
public class ImageTask extends AsyncTask<Photo,Void,Photo>
{
#Override
protected Photo doInBackground(Photo... photos)
{
Intent intent=getIntent();
Bundle bd=intent.getExtras();
String getId = (String) bd.get("id");
Photo finalPhoto=PhotoUtils.getImage(getId);
return finalPhoto;
}
#Override
protected void onPostExecute(Photo finalPhoto) {
tv1.setText(finalPhoto.getId());
super.onPostExecute(finalPhoto);
}
}
First, looks like you don't even need an Asynctask. Look at the example code
Secondly, you can't return it like that.
OnPhotoLoadedListener is an asynchronous callback.
Just use the image as normal within onComplete.
If you needed to pass back the image to the calling method, extract the callback to a parameter.
// this is void now
public static void getImage(String id, Unsplash.OnPhotoLoadedListener listener) {
unsplash.getPhoto(id, listener);
});
Call this as
PhotoUtils.getImage(id, new Unsplash.OnPhotoLoadedListener() {
// Use image in here
});
Which, if you look closely, all you did was replace normal usage of unsplash.getPhoto with PhotoUtils.getImage, which may be want you want, but you could accomplish a similar approach with a singleton pattern around the unsplash instance variable. That way, you're not rewriting all of Unsplash API calls
After using LeakCanary I found that there were many leaks in my app, most of them due to Volley's anonymous callback listeners. So I wrote a Util (below) class which uses static callbacks and WeakReference to keep reference to Context and an anonymous callback. But when I open the app for the first time, i.e. a cold start, the context is GCed soon after the request is made but during a warm start all works fine. Also this happens only for the first activity in the app.
Any alternative way of handling memory leaks with volley which works properly are also welcome.
public abstract class VUtil {
public static final String TAG = VUtil.class.getSimpleName();
public interface JsonCallback {
void onSuccess(JSONObject response);
}
public interface StringCallback {
void onSuccess(String response);
}
public interface ErrorCallback {
void onError(VolleyError error);
}
public static class JsonResponseListener implements Response.Listener<JSONObject> {
private final WeakReference<Context> mContextWeakReference;
private final WeakReference<JsonCallback> mCallbackWeakReference;
public JsonResponseListener(Context context, JsonCallback callback) {
mContextWeakReference = new WeakReference<>(context);
mCallbackWeakReference = new WeakReference<>(callback);
}
#Override
public void onResponse(JSONObject jsonObject) {
Context context = mContextWeakReference.get();
JsonCallback callback = mCallbackWeakReference.get();
if (context != null && callback != null) {
callback.onSuccess(jsonObject);
} else {
Log.d(TAG, "Context was GCed");
}
}
}
public static class StringResponseListener implements Response.Listener<String> {
private final WeakReference<Context> mContextWeakReference;
private final WeakReference<StringCallback> mCallbackWeakReference;
public StringResponseListener(Context context, StringCallback callback) {
mContextWeakReference = new WeakReference<>(context);
mCallbackWeakReference = new WeakReference<>(callback);
}
#Override
public void onResponse(String response) {
Context context = mContextWeakReference.get();
StringCallback callback = mCallbackWeakReference.get();
if (context != null && callback != null) {
callback.onSuccess(response);
} else {
Log.d(TAG, "Context was GCed");
}
}
}
public static class ErrorListener implements Response.ErrorListener {
private final WeakReference<Context> mContextWeakReference;
private final WeakReference<ErrorCallback> mCallbackWeakReference;
public ErrorListener(Context context, ErrorCallback callback) {
mContextWeakReference = new WeakReference<>(context);
mCallbackWeakReference = new WeakReference<>(callback);
}
#Override
public void onErrorResponse(VolleyError error) {
Context context = mContextWeakReference.get();
ErrorCallback callback = mCallbackWeakReference.get();
if (context != null && callback != null) {
callback.onError(error);
} else {
Log.d(TAG, "Context was GCed");
}
}
}
}
GC depends on many many things that are happening. One possible cause for your case is that when you do your first request after a 'cold boot' you app must init various custom objects, fragments, activities, views caches etc. and thus needs memory before increases the heap and thus do a GC.
The solution I propose however is to change your architecture.
1) it seems that u keep ref to context but it is never used. just drop it
2) you have Volley callbacks which delegates to your custom callbacks which you need to pass anyway, why don't you simply use 1 set of callbacks which you pass to the respective requests.
3) you WeekRef your custom callbacks but u cannot do without them. Week Referencing is not the ultimate solution to memory leaks. you have to find out why the ref is still there when you don't need it.
so if you leak issue is in JsonCallback, StringCallback and ErrorCallback implementations just try to figure this out instead of making the chain longer and cutting it at the end.
Thanks to djodjo's answer which helped me to reach a solution
Always use addToRequestQueue(request, TAG). Here TAG bit is what we'll use to cancel requests when their Activity/Fragment/View or anything is GCed
What i did is create a base activity and add all this request cancellation code in that activity. Here's what it looks like
public abstract class BaseActivity extends AppCompatActivity {
public final String tag;
public BaseActivity() {
super();
tag = getClass().getSimpleName();
}
#Override
protected void onDestroy() {
App.getInstance().cancelRequests(tag);
super.onDestroy();
}
protected <T> void addToRequestQueue(Request<T> request) {
App.getInstance().addToRequestQueue(request, tag);
}
}
cancelRequests is just simple code
getRequestQueue().cancelAll(tag);
Extend your activities from this BaseActivity and use addToRequestQueue to make requests, which will get cancelled automatically when your activity is destroyed. Do similar thing for fragment / dialog / whatever else.
If you make requests from anywhere else which doesn't follow a life-cycle, make sure that it's not binding to any Context and you'll be fine.
This just an example of my application.
I have created a singleton shared preferences which used a custom Shared preference manager class to edit the shared preferences data values:
public class MySharedPrefManager {
private static MySharedPrefManager instance= null;
private static SharedPreferences SharedPref;
private static Editor SPEditor;
private MySharedPrefManager () {
}
public static MySharedPrefManager getInstance(){
if(instance==null)
instance= new MySharedPrefManager ();
return instance;
}
public void setSharedPreferences(Context context){
SharedPref= context.getSharedPreferences("MySharedPref", Context.MODE_PRIVATE);
SPEditor= SPEditor.edit();
}
public void setAdmin(boolean pAdmin) {
SharedPrefManager.editBoolean("isAdmin", pAdmin, SPEditor);
}
public boolean isAdmin() {
return SharedPref.getBoolean("isAdmin", false);
}
}
Shared preference manager:
public class SharedPrefManager {
public static void editString(String key, String value, Editor pEditor){
pEditor.putString(key, value);
pEditor.commit();
}
public static void editBoolean(String key, boolean value, Editor pEditor){
pEditor.putBoolean(key, value);
pEditor.commit();
}
I have lots of activities which goes like activities: A->B->C->D->E->F->G
Activity A, which is the start-up activity, i get the instance of MySharedPrefManager and set the SharedPreferences also:
public class ActivityA extends Activity{
private MySharedPrefManager myPref;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.initialization);
myPref= MySharedPrefManager.getInstance();
// I am setting the SharedPreference Context with getApplicationContext(),
// as it is singleton, and I am using it through out my application
myPref.setSharedPreferences(getApplicationContext());
// other stuff...
}
private void changeData(){
myPref.setAdmin(true);
}
private void check(){
if(myPref.isAdmin()){
// do- something
}
}
}
ActivityD :
public class ActivityD extends Activity{
private MySharedPrefManager myPref;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.initialization);
// Here i called only the instance not the setSharedPreferences.
myPref= MySharedPrefManager.getInstance();
// other stuff...
}
private void changeData(){
myPref.setAdmin(true);
}
private void check(){
if(myPref.isAdmin()){
// do- something
}
}
}
Now, the issue I am facing right now is that, some times I get Null pointer exception in myPref.isAdmin() when I call it both in Activity A and D. But most of the time it works.
Also do I need to set the SharedPreferences (call the setSharedPreferences() method) in each of the activities? I don't feel the need to set is as it is singleton class.
I repeat, I am setting the SharedPreference Context with getApplicationContext(),as it is singleton and I am using it through out my application.
Edited: There are 3-4 shared preferences that I am using with the same structure.. With the similar problem. Using this PreferenceManager.getDefaultSharedPreferences(context);, I will be able to use only ONE SharedPreference which is the default SharedPreference.
The null pointer exception is probably caused by your singleton not being initialized. I suppose this happens when you rotate your device?
I expect the following to be happening: In activity A you do getInstance() and setSharedPreferences(). In activity B you only do getInstance() and expect to get the same instance that was created in A. But if you rotate the device while in B, then B is recreated which does the getInstance(), but this time it has to create its instance itself. And this time it will not do the initialization with setSharedPreferences().
My advise is to make your prefs class simpler. Make sure you initialize on construction. I.e. do not separate out getInstance() and setSharedPreferences(). Do something like this:
public class MySharedPrefManager {
private SharedPreferences sharedPref;
private MySharedPrefManager(Context context) {
sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
}
public static MySharedPrefManager from(Context context) {
return new MySharedPrefManager(context);
}
public void setAdmin(boolean pAdmin) {
sharedPref.edit().putBoolean("isAdmin", pAdmin).apply();
}
public boolean isAdmin() {
return sharedPref.getBoolean("isAdmin", false);
}
}
And use it like this when you need to do multiple settings:
MySharedPrefManager prefs = MySharedPrefManager.from(this);
prefs.setAdmin(true);
prefs.setSomethingElse();
boolean isFoo = prefs.isFoo();
Or use the shorter version if you only need one setting:
MySharedPrefManager.from(this).setAdmin(true);
Before using the data members or contents of the MySharedPrefManager, check for the null-check.
In MySharedPrefManager class add a getter method like this:
public SharedPreferences getSharedPreference(){
return SharedPref;
}
In the activity where you are accessing shared preference contents, in onCreate or onResume add this before using the contents:
myPref= MySharedPrefManager.getInstance();
if(myPref.getSharedPreference()==null){
myPref.setSharedPreferences(getApplicationContext());
}
This way you will be able to avoid NPE.
No need to, there already is a singleton. You can do:
PreferenceManager.getDefaultSharedPreferences(context);
I'm trying to select first root node of the CellTree after asynchronous data fetching from server. Here is my code:
public class MyTreeModel implements TreeViewModel{
private MyServiceAsync myService = GWT.create(MyService.class);
public <T> NodeInfo<?> getNodeInfo(T value) {
Cell<MyTO> cell = new AbstractCell<MyTO>() {
#Override
public void render(Context context, MyTO value, SafeHtmlBuilder sb) {
//rendering node...
}
};
return new DefaultNodeInfo<MyTO>(value instanceof MyTO ?
createBranchDataProvider((MyTO)value) : //fetching child nodes
cerateRootDataProvider(), cell); //fetching root nodes
}
#Override
public boolean isLeaf(Object value) {
if (value instanceof MyTO) {
MyTO to = (MyTO)value;
return to.isLeafNode();
}
return false;
}
private AbstractDataProvider<MyTO> cerateRootDataProvider() {
AsyncDataProvider<MyTO> dataProvider = new AsyncDataProvider<MyTO>() {
#Override
protected void onRangeChanged(HasData<MyTO> display) {
AsyncCallback<List<MyTO>> callback = new AsyncCallback<List<MyTO>>() {
#Override
public void onSuccess(List<MyTO> result) {
updateRowCount(result.size(), true);
updateRowData(0, result);
}
#Override
public void onFailure(Throwable caught) {
Window.alert(caught.toString());
}
};
myService.getRootNodes(callback);
}
};
return dataProvider;
}
private AbstractDataProvider<MyTO> createBranchDataProvider(final MyTO value) {
AsyncDataProvider<MyTO> dataProvider = new AsyncDataProvider<MyTO>() {
#Override
protected void onRangeChanged(HasData<MyTO> display) {
AsyncCallback<List<MyTO>> callback = new AsyncCallback<List<MyTO>>() {
#Override
public void onSuccess(List<MyTO> result) {
updateRowCount(result.size(), true);
updateRowData(0, result);
}
#Override
public void onFailure(Throwable caught) {
Window.alert(caught.toString());
}
};
myService.getChildNodes(value.getId(), callback);
}
};
return dataProvider;
}
For data which stored on client side solution looks pretty simple: we could just call something like
tree.getRootTreeNode().setChildOpen(0, true, true);
but if we want to fetch data asynchronously we will catch IndexOutOfBoundsException in case we try the same immediately after creation tree, because data is not obtained yet. How can I know when onSuccess() event will be fired in cerateRootDataProvider()? Or I could use another solution? Please suggest something.
I see two options for calling
tree.getRootTreeNode().setChildOpen(0, true, true);
in a right moment:
Direct dependency on presenter: add a constructor for your MyTreeModel object. Send corresponding presenter as a parameter. Add and call something like presenter.onDataLoadingComplete() at the end of onSuccess method.
Custom event: create your custom event. Fire it at the end of onSuccess method. Subscribe to it in some place where you can call access `tree``object. Call necessary code.