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
Many times we use intents to send data to a Fragment or get data back from a child. Can't we just put data in a public variable?
For example imagine if we want to get data from user from a dialog box.
I'm just talking about the "possibility". Undoubtedly, It is superior to use intents for code cleanness or safety...
you don't send intent's to fragments, if you want to use objects you need to have your object implement Parcelable then you can just send the object in the intent bundle
public class MyActivity extends Activity {
public int someValue = 1;
}
And in any fragment which has MyActivity as a host you can access ((MyActivity) getActivity()).someValue.
I think what he means is sending (local)broadcast... which is by the way the proper way of doing it according to my understanding.
Of course it is possible to have public (or even protected) fields and access them from a child-fragment with something like this:
assuming your parent activity is named "MainActivity"
((MainActivity) getActivity()).mMyPublicField
or:
((MainActivity) getActivity()).getPublicMethod()
- but I would never recommend doing this!
especially when you also start manipulating the public field you can run into ugly trouble when different threads are in play.
If something needs so be shared across the whole application, use SharedPreferences (if you want to store it for the next app session too) or as I mentioned first LocalBroadCastManager.
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 have just started android development 3 days ago and I'm trying to develop a messenger for Yahoo. Hell of a first application, right?! :D
The problem I'm facing right now is that I'm trying to implement a tabbed IM window which is supposed to show people that I'm currently chatting with. I designed a layout file and the necessary activity class for a single chat window and I am using a TabActivity to show several of those to the user.
However the problem is that I am storing some chat-specific information (such as the ID of the user I am currently chatting with) in the activity class itself and I am facing problems initializing those values when a new tab is created.
Initially I used a BroadcastReceiver and an intent to initialize like so:
protected class MyListener extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
if (intent.getAction().equals(
"com.sirm2x.messenger.CHAT_WINDOW_CREATED"))
{
ChatWindowActivity.this.friendId = intent.getExtras()
.getString("friendId");
}
}
}
The problem is that the friendId of all the activities are set to the value passed for the last tab-to-be-created!
What is the solution/best practice for situations like this? Am I even approaching the problem correctly?
Any help is hugely appreciated!
The problem here is that you register a Broadcast receiver in each or your tabs, and each of your receiver receives the broadcast, setting the friendId variable in it.
I am not quite sure BroadcastReceiver is the correct approach here.
I am a newbie to android. My question is not about how to do something, but more on the idea I have in mind is optimized or not.
I am creating a Chat App. The biggest issue I was facing was storing Non persistent data, coz whenever the activity closed, all data was lost. The biggest problem was when user moved from Chat Screen (Chat Activity) to Peoples List (Peoples Activity) all data was lost again, and if user reinitited chat, he couldnt see the history.
As a workaround, I am creating a few data classes, and a service. The service stores data in the classes, whenever it receives an update from activity or the server. After that on each new activity I will just pass around this object from one activity to another and service.
I would like some recommendations in this, Is this a good way to go around? Thanks for your precious time.
If I've understood properly, you need a way to store data of variables or the content of one data structure or whatever and don't lose this data when your app change across severals activities, right? You need save state across several Activities.
First solution: in Java, one solution for this problem could be to use "static" variables. You can do it but using Android, we can use a more elegant solution.
Second and recommended solution: Associate the state with the Application Context (easy)
You should create your own subclass of android.app.Application. It will work like a singleton.
One subclass of Application inherit the properties of Application and you can access to this class wherever you want using the command "Context.getApplicationContext()". Normally you will use this class to have everthing that need a global access. Example:
class YourName_App extends Application {
private ArrayList<String> chatConversation;
public String getChatConversation(){
return this.chatConversation;
}
public void setChatConversation(ArrayList<String> chat){
this.chatConversation = chat;
}
}
And now your Chat Activity:
class Chat extends Activity {
ArrayList<String> conversation;
#Override
public void onCreate(Bundle b)
{
...
YourName_App appState = ((YourName_App) getApplicationContext());
conversation = appState.getChatConversation();
...
}
}
It is done! This is the best way to do it.
Sorry for my poor english.