Dispatch user action to activities in backstack - android

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.

Related

Moving data back and forth between two open activities

I have two activities- act1 and act2. I want to transfer a String from act1 to act2 when the user presses a button and after some computation I want to transfer a LatLng from act2 to act1, while keeping both activities open (or at least be able to restore every change in act2 from the app start).
Things I tried:
I saw here that if I add some flags to the intent I won't start new activities when using startActivity(). It works well getting from act1 to act2 but after starting act1 from act2 it stops act2, so in the next act2 opening it will make a new act2 copy.
I looked at the not-deprecated version of startActivityForResult, but since I need both of the activities open it's not what I want. I also want both of them to receive and return data.
From here, using static data structure in one activity may not be good practice.
I tried using extras and onNewIntent but couldn't make it work (since I can't keep both activities running).
Background (if relevant):
I have an app with two activities- main, which stores a ListView, and a map activity.
The map must have a marker placed in each saved address.
The list has two button types:
"Add a new place...", that open the map activity, and adds a new marker where the user wants.
-some address-, that open the map in the location corresponding to this address.
I need a way to pass the desired address to the map so it'll show this location, and I also need to pass the new saved address back to the main activity after creating a location.
You can use the repository pattern to store the data in a way accessible by all code in your app.
What is a repository?
A repository is a simple class that holds some data and provides access to it. Something like:
class Repository {
private String theString = "";
public void setTheString(String newValue) {
theString = newValue;
}
public String getTheString( {
return theString;
}
}
It's a good idea to give a more descriptive name to the repository. So if this is a repository that stores location data, you could name it LocationRepository.
Where should it be created?
A good place to keep the reference to it is the Application class. If you don't have one, you can define it like this:
class MyApplication extends Application {
private Repository repository = new Repository();
public Repository getRepository() {
return repository;
}
}
It needs to extend the Application class provided by the android framework
You can also override the onCreate() method for more complex initialization, if necessary in your case
Define the application class in the manifest
In order for the android framework to know about your custom Application class you need to define it in the AndroidManifest.xml:
<application
android:name=".MyApplication"
...
Access the repository in your activities
Now in each activity you can cast the application context into your application class:
class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Repository repository = ((MyApplication) getApplicationContext()).getRepository();
System.out.println(repository.getTheString());
}
}
If you want a cleaner approach, where you care about architecture design:
Create a ViewModel per each activity
Create a Singleton, which the ViewModels access, and use it to share the data.
The Singleton could be something like:
object AddressManager{
fun saveAddress(address: Address) {
}
fun getAddress(): Address{
}
}
And of course, I would advise you to use Dagger and Hilt for the injection and not to use "object", but this is again more and more design ideas.

How to structure Android dialogs that sends API calls

As a background, I am using MVP mosby for my android application.
Currently I have this UI design requirement, where from almost everywhere across the app. (4 different activity/fragment/recycler adapter). If the user taps on an item, I should present a dialog (same everywhere) and the dialog itself needs to make API calls, and need to handle any error that comes back.
I wrote this the present dialog logic inside a helper class.
#EBean
public class DialogService {
Dialog d;
public void presentUniversalDialog(Context context, Data data) {
d = new Dialog(context);
.. set view
.. change some text view based on data
.. make some api calls
}
private void makeAPiCall() {
.. some API calls here. On Return, update the dialog d if not null
}
}
So then in my other activities, I just need to inject this service and I can easily show the dialog by calling
#EActivity
public class MyRandomActivity extends Activity {
#Bean
DialogService dialogService;
#Click(R.id.my_random_button)
void onButtonClick() {
dialogService.presentUniversalDialog(this, data);
}
}
Now, the good news is that all the random activities should not be bothered by this dialog as long as it is launched. So I don't need to pass random event listeners around.
But how do I structure my dialogService code to deal with async events?
For example, the "data" field might contain only an id so I need to make API calls to populate the whole data. And once user clicks on OK. I need to send a request to confirm.
For now, I worked around by basically keeping track of the API calls via some member fields inside the DialogService. But as code gets large, this will quickly fill up and starts to be super confusing.
What is a recommended way of writing this "universal dialog"? Should I perhaps only use service per dialog? Or are there some other ways?
Treat dialog as View (in MVP) and give it its own presenter as gateway to your business logic (to make http request). So just treat Dialog not different than you would treat a Activity or Fragment in MVP.
Also worthwhile checking DialogFragment

Get root view of one activity from another activity

Is there a way to get a root view of one activity from another lets say there are two activity A and B i want root view of activity A from B.please Help
Edit: I am Trying to make change in Main Activity from Preference Activity for ex i want to change the text of TextView in Main Activity as soon as i select a checkboxpreference from preference activity.
A stopped, or hidden activity may no longer be present, to free up resources. It is not a good idea to reference objects from other Activities.
To send an event/data from one Activity to another Activity you may use:
Start newer activity using startActivityForResult(). And get results back via onActivityResult().
Share some data is a third object, accessible to all activities. Most likely a singleton or Application class.
Use EventBus and post sticky events. New subscribers will automatically get these events. This method works for all kinds of objects, not just activities.
I'm not sure what you would need this for, it sounds a little dangerous, but the root view probably won't exist without running first, but you MAY be able to get the root view while it's running and pass it to Activity B, but the view shouldn't be active while it's not running, thus will be null.
Create it as static and pass the rootView to it on OnCreate, or pass it to the Application class so it persist between entire application. Verify if its null before doing operations.
I never tryed it, but my guess is that while activity is not finished the rootView can be accesed (its status stays on pause while another activity is active).
Look like you are trying to trigger an action across activities when your preference changed. I suggest to use LocalBroadcastManager to send the message from ActivityB to ActivityA. I assume that your ActivityB is the preference and active activity, and ActivityA is in the back stack:
ActivityB.java
mListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// create your broadcast intent and pass the value of changed preference
Intent intent = ...
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
};
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(mListener);
ActivityA.java
mBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// handle your change to ActivityA here
}
};
// create intent filter for your preference change broadcast
IntentFilter intentFilter = ...
LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
Given that your ActivityA is not yet destroyed, it will be able to catch the broadcast and you can achieve what you want.

How to get results from an IntentService back into an Activity?

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.

Activities interactions in Android

I am working on an Application that require some interaction between two activities, and I am not sure of what is the best way to achieve it:
One of the Activities is a "Logbook" (Just a ListView that displays a bunch of events).
The other Activity allows the user to create the events, that will be sent (and displayed in the Logbook).
How do I notify my Logbook Activity when a new Event is ready to be added?
Also, where should I add the event to the database? From the Logbook Activity, when I add it to the ListView, or from the NewEvents Activity, as soon as it's ready?
Thanks!
Ok, I found how to do it, using a BroadcastReceiver:
In my Logbook activity, I just set up a new custom receiver onCreate():
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_logbook);
registerReceiver(new EventReceiver(this), new IntentFilter("SEND_NEW_EVENT"));
Now, I can make the calls in my newEventActivity:
public void sendToLogbook(int eventId){
Intent i = new Intent("SEND_NEW_EVENT");
i.putExtra("newEvent", this.newEvents[eventId]);
sendBroadcast(i);
}
Of course, I had to create my CustomReceiver Class, and override the onReceive() method to do what I want:
public class EventReceiver extends BroadcastReceiver {
private ActivityLogbook activity;
public EventReceiver(ActivityLogbook activity) {
this.activity = activity;
}
public void onReceive(Context context, Intent i) {
this.activity.addToReport((Event)i.getParcelableExtra("newEvent"));
}
}
It works great so far, but if you do have comments/concerns about this, please tell me!
Thank you!
If I recall cporrectly the Notepad project which is included in the android sdk and is also part of the tutorials online is a good examaple which should satisfy your needs.
To borrow from MV-* (Model-View-something or other) patterns, separate your idea of the Model (in this case, your Event objects) and what is displaying them (the View, or in your case an Activity) and it'll become more clear.
If you have your events somewhere global where all activities can interact with them, then you can work with the model and display the model from wherever and however you choose.
One simple suggestion is have a class (EventController or something like that) that allows you to interact with the Events collection, and make it available through a derived Application class. I can explain further if that doesn't make sense. I have a pattern I use in my Android apps whereby all Activity classes have access to a custom global Application instance, so my model is a model and can be accessed by whatever Activities I want to have access.
This is merely one approach, and as always, there are many that may suit your needs.
One possibility would be:
The ListActivity gets all the data each time it is resumed and updates the ListView accordingly
The NewEventActivity does all the job of storing the Event and simply finishes
You can improve it a bit more:
The ListActivity gets all the data when it starts
The ListActivity starts the NewEventActivity expecting a OK/CANCELLED result
The NewEventActivity does all the job of storing the Event and returns a result saying OK or CANCELLED
Depending on the result it gets from the NewEventActivity, ListActivity reloads its data or not

Categories

Resources