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.
Related
I am trying to perform a callback within a Async Task (communicate to Cloud server) to different Activity method. As my design will call the same Async Task in different Activity.
Now I am using the method which require checking the Activity name. Is there a way I could pass a generic method function (like C code) into "LoginAccount" during the call ?
public void loginAccount(final Activity activity, final String email, final String password) {
Start a Async task, then
.......
return of Async task here
#Override
public void onSuccess(Object value) {
String strName = activity.getClass().getSimpleName();
if (strName.equals("MainActivity"))
((MainActivity)activity).updateDeviceName();
else if (strName.equals("LoginActivity"))
((LoginActivity)activity).processLoginResult();
} }
}
You can't pass functions as parameters in java but the closest you can get to this kind of behavior is to pass a Runnable object.
Runnable | Android Developers
Why does your task do very seemingly very different things? You should probably just make two different classes that extend AsyncTask<>. Then use the respective AsyncTask in each Activity.
I end up using different approach to resolve this problem. I had created a "Service" to handle the Cloud activity at background. Each Activity would bind to Service and use Messenger to trigger the Async method.
This case, I have prevented Activity being destroyed and callback is gone. As Service will not be changed until Apps is closed.
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.
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.
I am working on database for a game.
I would like to get the score from one activity and call it in database activity, where it will be updated in the database by using intent, the game gets paused when intent is fired.
I have also tried using application class (suggestions on that are welcome).
Is there any way to pass data among activities by firing intent in background so that it does not interrupt the gameplay?
Your database should not be in an Activity. An Activity represents one screen (or one use-case) in your application, and should not represent implementation details like a database-class.
As an alternative, you can put your database code in a Singleton class that can be accessed from everywhere in the same application. You can also create the database in the Application's onCreate method and store it in a static field there or just create a separate class that exposes static methods to access the database.
If you are somehow dead-set on using Intents, you would need to use a database Service instead of an Activity. This way the current Activity will remain on top when you send the data to the service. But that would complicate things a lot compared to a simple static class/singleton approach.
You seem to have a big mix up: There shouldn't be a database activity. You should create a database helper class and make it a member variable in your application class.
// inside application class
private static MyDatabaseHelper mMyDB;
public MyDatabaseHelper static getDatabase() {
if (mMyDB == null) {
// mInstance should be the application instance, means make the
// application class a singleton
mMyDB = new MyDatabaseHelper(mInstance);
// the constructor should open/create the database
}
return mMyDB;
}
With this you can work with your database from everywhere.
With that you can easily update every value from everywhere. So no need to fire an intent to pass values around. Instead just update your database directly.
There are multiple ways to this.
You can use handlers, services, then use setresult which give call back to onactivity result of the parent activity.
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
Bundle b = new Bundle();
b.putInt("key", 1);
intent.putExtras(b);
startActivity(intent);
I'm trying to make a helper class to start an Activity and get the return result (startActivityForResult) to avoid developers writing their own onActivityResult code and encapsulate the complex internal details.
ie: the caller's code:
MyIntent i = new MyIntent();
i.getMyData(new OnData() { public void onData(Bundle data) {....} );
I tried creating a "dummy" Activity inside MyIntent just to be able to override onActivityResult, but the activity needs to be declared in the manifest, which is what the helper class tries to avoid. There is no "setOnActivityResult" which would be a good alternative.
So, how to create an Activity programmatically and "attach" it so it has valid state?
After creating new Activity() I'd like to call the Activity "attach" which is internal.
So, how to create an Activity programmatically and "attach" it so it has valid state?
That is not possible, sorry.
I'm trying to make a helper class to start an Activity and get the return result (startActivityForResult) to avoid developers writing their own onActivityResult code and encapsulate the complex internal details.
That is not possible, sorry. The closest you can get is how the Barcode Scanner integration JAR works -- you delegate onActivityResult() to them to decode the result obtained from the scanner activity.
Could a simple callback be an alternative? User places callback in static field of your library and your library will invoke this callback when required. The most straightforward implementation could be:
YourSdk.staticCallbackField=new OnData() { public void onData(Bundle data) {....});
MyIntent i = new MyIntent();
startActivity(i);
When you SDK completes it's job it invokes callback:
staticCallbackField.onData(data)
User activity will get Bundle data in callback instead of onActivityResult.
You should be aware of possible activity lifecycle issues. For example if android recreates user activity in the background callback should be recreated.