I'm developing social app. Let's assume I have a stack of activities A -> B -> C -> D.
D is in foreground and user presses "like" button to something there (post, comment, user etc.) What is the best way to notify all other activities about this action in order to refresh their data? I see 3 options here:
Use local database and some loaders to automatically refresh the data. However, it requires a lot of code if we have different data-models with shared data (for instance BasicUserInfo, UserInfo, DetailedUserInfo).
Use EventBus with sticky events (producers for Otto). In this case I must notify ONLY backstack activities and ignore those that will be created. Also I have to manage events overriding.
Use a simple observer pattern with WeakReferences to backstack activities. But then I have a problem with killed activities that are going to be re-instantiated.
Real example:
In Instagram: I open some specific user's profile (A), there I open some specific post (B), again profile (A) and so on A -> B -> A -> B -> A .... So it loads data from the web everytime. On the step "n+1" a new comment to the post appears. If I start going back through my backstack I will see that instagram has dispatched this "new" comment to all B activities without reloading any data from web. So I'm interesting how do they do it.
The main use case for a notification system (event, observer, BroadcastReceiver, ...) is when you want the recipient to act more or less immediately when something happens.
I think this is not the case here : the backstack activities don't need to act immediately as they are not visible. Besides they may even not exist anymore (killed / frozen). What they actually need is to get the latest data when they are back on the foreground (possibly after having been recreated).
Why not simply trigger the refresh in onStart() or onResume() (using a Loader or anything you already use) ?
If the 'liked' status needs to be persisted you could do it in D's onPause().
If not, the liked object could be stored in a global variable (which is actually what a sticky event is)
You can use LocalBroadcastManager to notify your stacked activities that your Liked event has occured
Suppose in your Activity D:
private void liked() {
Log.d("liked", "Broadcasting message");
Intent intent = new Intent("like-event");
// You can also include some extra data.
intent.putExtra("message", "my like event occurs!");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
Now it will notify all the activities who are registered with this broadcast reciever
for example in your Activity A,B,C :
#Override
public void onCreate(Bundle savedInstanceState) {
...
// Register to receive messages.
// We are registering an observer (mMessageReceiver) to receive Intents
// with actions named "custom-event-name".
LocalBroadcastManager.getInstance(this).registerReceiver(mLikeEventReceiver ,
new IntentFilter("like-event"));
}
// Our handler for received Intents. This will be called whenever an Intent
// with an action named "like-event" is broadcasted.
private BroadcastReceiver mLikeEventReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Get extra data included in the Intent
String message = intent.getStringExtra("message");
Log.d("receiver", "Got message: " + message);
}
};
#Override
protected void onDestroy() {
// Unregister since the activity is about to be closed.
LocalBroadcastManager.getInstance(this).unregisterReceiver(mLikeEventReceiver );
super.onDestroy();
}
references :
[http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html][1]
[how to use LocalBroadcastManager?
[https://androidcookbook.com/Recipe.seam?recipeId=4547][3]
[1]:
http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html
[2]:
how to use LocalBroadcastManager?
[3]: https://androidcookbook.com/Recipe.seam?recipeId=4547
in order to refresh their data?
Here lies the answer. Activities should not own data. Activities present data and allow user to act upon it. Data itself should be in a separate entity that can be interacted with by activity instances, a Model.
Further, it should not be assumed that there is always an activity instance in back-stack. Those activities can be destroyed and later re-created as different objects by system when user navigates back. Data is always refreshed when the whole activity is being created.
Separate out data handling to specialized classes, that can be accessed by activities easily, and can provide data / event binding to activities on demand. A bound Service is a good candidate.
As far as not loading data from web is concerned, you can setup a local cache of most recently accessed data (cache, because mobiles have strict storage limits, server and db not so). So, any change from user side is also committed to this cache along side propagating to the server. All this better be encapsulated by specialized data classes, rather than relying on a back-stack or special code in activity classes.
As a pattern you can construct Model classes for data entities involved. Write a web API interface implementation to talk to the server. And then, place a cache layer before the API interface. The cache would retain outgoing changes and incoming updates to/from API layer and simply reflect data requests when server call is not needed.
Cache has to do 3 things mainly:
Evict: as new data comes, drop the least important data, so cache remains of a fixed size. Most cache implementations (like this one) do this automatically.
Invalidate: Some times, due to a user action, or external event on server side some data has to be refreshed.
Expire: Data can be set with a time limit and will be auto evicted. This is helpful when enforcing a periodic refresh of data.
Now most cache implementations out there deal in raw bytes. I'd recommend using something like Realm, an object db and wrap it in cache like features. Hence a typical flow of requesting user tweets would be:
Activity is displayed.
Activity binds to data service, and expresses its interest in "tweets".
Data service looks in Tweets table of cache db for last fetched list of tweets and immediately returns that.
But, Data service also calls the server to give the tweets after time-stamp of latest tweet it has locally in db.
Server returns latest set of tweets.
Data service updates all the bound activities who expressed interest in tweets, with new incoming data. This data is also replicated locally in cache db. At this point, tweets table is also optimized by dropping old records.
Activity unbinds from Data service, as the activity is going away, stopped, destroyed etc.
If no bound activity is interested in "tweets", Data service stops loading more tweets.
Your data implementation can go a level further by maintaining socket connections to server and receive interesting changes in real-time. That is step 5 above will be called by server whenever there is new data incoming.
TLDR; Separate data management from Activities, and then you can evolve it independently of UI concerns.
The classic way of handling this type of thing is to use BroadcastReceivers.
Here's an example receiver:
public class StuffHappenedBroadcastReciever extends BroadcastReceiver {
private static final String ACTION_STUFF_HAPPENED = "stuff happened";
private final StuffHappenedListener stuffHappenedListener;
public StuffHappenedBroadcastReciever(#NonNull Context context, #NonNull StuffHappenedListener stuffHappenedListener) {
this.stuffHappenedListener = stuffHappenedListener;
context.registerReceiver(this, new IntentFilter(ACTION_STUFF_HAPPENED));
}
public static void notifyStuffHappened(Context context, Bundle data) {
Intent intent = new Intent(ACTION_STUFF_HAPPENED);
intent.putExtras(data);
context.sendBroadcast(intent);
}
#Override
public void onReceive(Context context, Intent intent) {
stuffHappenedListener.onStuffHappened(intent.getExtras());
}
public interface StuffHappenedListener {
void onStuffHappened(Bundle extras);
}
}
And how to attach it to an activity:
public class MainActivity extends AppCompatActivity implements StuffHappenedBroadcastReciever.StuffHappenedListener {
private StuffHappenedBroadcastReciever mStuffHappenedReceiver;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mStuffHappenedReceiver = new StuffHappenedBroadcastReciever(this, this);
}
#Override
protected void onDestroy() {
unregisterReceiver(mStuffHappenedReceiver);
super.onDestroy();
}
#Override
public void onStuffHappened(Bundle extras) {
// do stuff here
}
}
"onStuffHappened" will get called as long as the activity is alive.
I agree with #bwt 's approach. Those notification systems should matter when you want to notify something immediately.
My approach would be a cache system. You don't need to deal with "if the activity is back stacked or newly created", you always need to query what you need in the onResume of the activity. So you will always get the most up to date data.
While fetching data from your server, you also need a copy of your data models in a local database. Before you ping your server when, e.g. there is a like in a user post, you need to set this as a Flag in local database, and send your Http Request to your server to say "Hey this post is liked". And later when you get the response from this request, if it's successful or not, attempt to modify this Flag again.
So in your Activities, you will get most up to date data if you Query from local database in onResume. And for changes in other users' post, you can have a BroadcastReceiver to reflect the changes in your Visible activity.
P.S: You can check Realm.io for relatively faster queries, since you will need to have local database calls quicker.
As a lot of other answers have eluded to, this is a classic example where a BroadcastReceiver would easily get the job done.
I would also recommend using the LocalBroadcastManager class in conjunction with BroadcastReceiver. From the documentation of BroadcastReceiver:
If you don't need to send broadcasts across applications, consider using this class with LocalBroadcastManager instead of the more general facilities described below. This will give you a much more efficient implementation (no cross-process communication needed) and allow you to avoid thinking about any security issues related to other applications being able to receive or send your broadcasts.
If you have to make changes in all activities with respect to some data you can follow the interface pattern .For example you have an custom class class ActivityData which have the content you need to update in all activities
Step 1:
Create an interface as follows
public interface ActivityEventListener
{
ActivityData getData( Context context,
Activity activity );
}
Step2:
Create a BaseActivity for referencing all for u r activities which u have in you application and implement the interface as follows
public class BaseActivity extends Activity
{
protected AcitivityData mData= null;
#Override
protected void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
}
protected ActivityEventListener mListener = new ActivityEventListener()
{
#Override
public ActivityData getData( Context context,
Activity activity )
{
// TODO Auto-generated method stub
return mData;
}
};
}
step 3:
Extends your own activity with BaseActivity for example A or B or C .......
public class A extends BaseActivity
{
ActivityData mData;
#Override
protected void onCreate( Bundle savedInstanceState )
{
mData = mListener.getData(this,this);
updateWidgets(mData);
}
}
where updateWidgets is a function where you can define UI elements and use the data what u have with interface
As all of your activities B/C/ so on can get the reference of ActivityData. Activities form backstack will start execution through onStart() user can handle activities in same nature with the details exist in ActivityData
In your case when you like in last activity you can update the object of ActivtyData and when back stack activity resumed or started you can get updated data as your backstack activity extends with BaseActiivty having interface.
I'm developing an Android module that basically consists of a custom View third parties can just drop into any of their activities.
This module includes a configuration activity (based on PreferenceActivity).
Given that you can't start an activity from a View, the module calls 'openConfig(Intent intent)' on the activity it is displayed in. This activity acts as a delegate for the module.
That works fine thus far (though I'd really like the module to handle everything internally, no delegate methods required, but I reckon that just isn't possible).
However, I need some sort of callback from the preference activity to the module, so that the module will get notified when the settings have been changed.
I was thinking of just adding the module's main class as a delegate to the preference activity;
ConfigActivity a = new ConfigActivity();
a.testVar = "testtest";
Intent intent = new Intent(getContext(), a.getClass());
delegate.handleConfigAction(intent); //
However, this test with just a simple String (instead of an interface) showed, that the String wouldn't get set after the activity has been started.
Second thought was to use 'putExtra()' on the intent, but that doesn't really suit the use case as the delegate I'd like to put there really is a View and not a serializable data object.
Are there any ways for me to handle this internally? I am aware of the 'onActivityResult()' function (http://developer.android.com/training/basics/intents/result.html), but that would mean that the third party developer using my module would need to handle this, something that needs to be avoided for obvious reasons.
Any help would be highly appreciated!
EDIT: FINAL SOLUTION
In the end I've changed the module from View to Fragment, which now works much better with handling "child" activities and such. When starting an Activity from a Fragment, the 'onActivityResult' function works beautifully to accomplish the task at hand.
You can start an Activity from a View using its Context as in:
#Override
public void onClick() {
Intent intent = new Intent(getContext(), ConfigActivity.class);
intent.putExtra("testVar", "testtest");
getContext().startActivity(intent);
}
You could use the onWindowVisibilityChanged() method to read the configuration that would be set in the ConfigActivity to make your View change its behavior.
I have an activity such that when the user onPause() on that activity, i start a pending intent that modifies some variable such as mSomeIntegerThatNeedsToBeReset after some time (using Alarm Manager and pending intents).
I tried to make this variable static, which works, but i don't want to make things static if i can help it, and i need to reset that value (in activity) from somewhere else.
public class SomeActivity extends Activity {
private Integer mSomeIntegerThatNeedsToBeReset;
}
what's the standard way to access this variable?
Sounds like you either need to bind your service to your activity OR
You could dispatch an Intent from your service to your Activity and clear the state variable.
I am using an IntentService to handle network communications with a server via JSON. The JSON/server part is working fine, but I'm having trouble getting the results back where they are needed. The following code shows how I am starting the intent service from inside onClick(), and then having the service update a global variable to relay the results back to the main activity.
public class GXActivity extends Activity {
private static final String TAG = "GXActivity";
#Override
public void onCreate(Bundle savedInstanceState) {
// === called when the activity is first created
Log.i(TAG, "GXActivity Created");
super.onCreate(savedInstanceState);
setContentView(R.layout.start);
View.OnClickListener handler = new View.OnClickListener() {
public void onClick(View v) {
// === set up an application context to hold a global variable
// === called user, to contain results from the intent service
GXApp appState = ((GXApp) getApplicationContext());
User user = new User(-1); // initialize user id to -1
appState.setUser(user);
// === next start an intent service to get JSON from the server;
// === this service updates the user global variable with
// === results from the JSON transaction
Intent intent = new Intent(this, GXIntentService.class);
startService(intent);
// === check app context for results from intent service
appState = ((GXApp) getApplicationContext());
if (appState.getUser().getId() != -1)...
}
}
}
}
The problem I'm having is that the intent service that parses the JSON doesn't get invoked until after onCreate() completes, so my code that's looking for the results is stuck looking at uninitialized results.
What should I do differently so the intent service gets called before I check the results? Would it work if I pulled the click listener out of the onCreate() function? Is there a another/better to structure this code? Many thanks.
You should look at creating your own ResultReceiver subclass in your activity. ResultReceiver implements Parcelable so can be passed from your Activity to your Service as an extra on the Intent.
You'll need to do something like this:
Implement a subclass of ResultReceiver within your activity class. The key method to implement is onReceiveResult(). This method provides you a with Bundle of result data which can be used to pass whatever information you are retrieving in your Service. Simply unpack the data you need and use it to update your activity.
In your activity, create a new instance of your custom ResultReceiver and add it to the Intent you use to start your service.
In your Service's onStartCommand() method, retrieve the ResultReceiver passed in on the Intent and store it in a local member variable.
Once your Service completes its work, have it call send() on the ResultReceiver passing whatever data you want to send back to the activity in a Bundle.
This is a pretty effective pattern and means you're not storing data in nasty static variables.
There's many ways to do stuffs in background getting back a result (AsyncTask, bind an Activity to a Service...), but if you want to mantain your IntentService's code you can simply:
1- send a broadcast intent (that contains the status in its extended data) at the end of the work in your IntentService
2- implement a LocalBroadcastReceiver in your Activity that consumes the data from the Intent
This is also the recommended way in the official documentation (if you want to mantain your IntentService):
https://developer.android.com/training/run-background-service/report-status.html
If all you need to do is get a JSON from the server without locking up the UI thread, it may be simpler to just use AsyncTask.
You can use Otto library.
Otto is an Open Source project designed to provide an event bus implementation so that components can publish and subscribe to events.
How would I get access to the "main" activity from inside a BroadcastReceiver?
public void onReceive(Context context, Intent intent)
UPDATE: I am trying to get a reference to the activity from within the Broadcast Receiver executes.
( Android newbie here... please be gentle :)
"Main" activity can mean lot of things - it can be the currently foreground Activity; the Activity that is launched from the app's icon on the home screen; or an tabbed Activity that holds another Activities as tabs. I presume that by "access" you mean to hold an reference to the Activity object.
In any case, if the Activity is know to have only one meaningful instance constructed at any given moment, you can make it singleton.