startActivityForResult not working properly with launchMode singleInstance - android

I'd like Activities on my application's Activity stack to only have one instance. I have several screens which are ListActivities and I'd like to not go through the pain and suffering of updating the lists in a previous instance of the ListActivity when another instance of that ListActivity is changed (added to, edited, removed from, etc) (or is there an easy way to do this?).
Note: I've read that singleTop will accomplish this (though it destroys the Activity if you hit the back button), but it does not work. I have a menu and if I go to my Inbox screen, then I go to my QuickList screen, and then I go to my Inbox screen again, it creates a new Inbox Activity.
Right now, on my ListActivities, I have launchMode set to singleInstance. The problem is: If I launch another Activity using startActivityForResult, the onActivityResult handler fires right away (before the new Activity is created). When I perform the necessary action on the next screen to return the result, the onActivityResult handler does not fire.
What is going on?
Here is how I fire the new Activity:
Intent intentLaunchQuickList = new Intent(ActivityMyList.this, ActivityQuickList.class);
startActivityForResult(intentLaunchQuickList, REQUEST_QUICKLIST);
Here is how I return the result:
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
QuickListItem qlItem = m_Adapter.getItem(position);
if (qlItem != null && qlItem.getQLId() != -1) {
Intent data = new Intent();
data.putExtra("ql_id", qlItem.getQLId());
if (getParent() == null) {
setResult(Activity.RESULT_OK, data);
}
else {
getParent().setResult(Activity.RESULT_OK, data);
}
}
finish();
}
Here is my onActivityResult handler:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_QUICKLIST) {
if (resultCode == Activity.RESULT_OK) {
Bundle extras = data.getExtras();
if (extras != null) {
int id = extras.getInt("ql_id");
if (id > 0) {
launchQLItemsThread(id);
}
}
}
}
}

From the documentation of startActivityForResult: "For example, if the activity you are launching uses the singleTask launch mode, it will not run in your task and thus you will immediately receive a cancel result." singleInstance activities are the same way.
In other words, if you want to use sAFR, you will need to handle multiple activity instances. What I would advise is storing the list state for your ListActivity instances in onPause to some app-global spot (a singleton or whatever), and loading from there in onResume. Then, even if multiple ListActivity instances will get created, the top one will always update the data before the older ones get resumed, and the lists will always appear current to the user.
Note that you should be doing that anyway if your data is meant to be persistent, because your whole process can be killed by the system any time after an onPause call, and if you haven't saved any changes somewhere by the time that returns, they are liable to get silently lost under some -- often rare and unpredictable -- circumstances. In this case you want to be using local files or SQLite databases, not persisting to the network. onPause needs to return quickly because the user can't interact with the system while it's running, so save to local storage and then sync to the network at some other time, perhaps via a service launched by onPause.

I have several screens which are
ListActivities and I'd like to not go
through the pain and suffering of
updating the lists in a previous
instance of the ListActivity when
another instance of that ListActivity
is changed (or is there an easy way to
do this?).
Use a consistent model. For example, your data is hopefully in a database. Each ListActivity has a Cursor on the portion of the database it needs. Have that Cursor be a "managed Cursor" (via startManagingCursor()), and your ListViews will update automatically in onResume(). You then make your changes to your model via the database.
I have a menu and if I go to my Inbox
screen, then I go to my QuickList
screen, and then I go to my Inbox
screen again, it creates a new Inbox
Activity.
That's what it is supposed to do. Quoting the documentation:
The "standard" and "singleTop" modes
differ from each other in just one
respect: Every time there's new intent
for a "standard" activity, a new
instance of the class is created to
respond to that intent. Each instance
handles a single intent. Similarly, a
new instance of a "singleTop" activity
may also be created to handle a new
intent. However, if the target task
already has an existing instance of
the activity at the top of its stack,
that instance will receive the new
intent (in an onNewIntent() call); a
new instance is not created. In
other circumstances — for example, if
an existing instance of the
"singleTop" activity is in the target
task, but not at the top of the stack,
or if it's at the top of a stack, but
not in the target task — a new
instance would be created and pushed
on the stack.
(boldface added for emphasis)
Right now, on my ListActivities, I have launchMode set to singleInstance.
Please do not do this.

Related

Android - Activity seems to not get called after the intent returns

I have an activity where you can click on a button that sends you to an activity where you can add pictures (using startActivityForResult). You can add a couple of pictures. Every picture you add, gets converted to a byte[] array, and gets stored as an extra in the result intent. When the second activity finishes, if i added one picture, and finish the activity, everything is fine, it returns to the previous activity with the button. If i add two or more pictures, and finish the second activity, the previous activity doesn't even get called. It takes me directly to an activity before the activity with the button, but first i see like a black screen that seems like the previous activity crashed, though no errors can be found in the logs. The onActivtiyResult in the previous activty does not get called as well. Any reason why this might be happening ?
Code sending intent to photo adding activity :
Intent addEditPicturesIntent = new Intent(getApplicationContext()
,AddOrEditPicturesOnProductActivity.class);
startActivityForResult(addEditPicturesIntent,33);
Code finishing the photo adding activity:
Intent returnIntent=new Intent();
if (hashMap.containsKey("one")){
Log.d(TAG,"Submit Button clicked and now putting extra in intent-->1");
returnIntent.putExtra("1",hashMap.get("one"));
}
if (hashMap.containsKey("two")){
Log.d(TAG,"Submit Button clicked and now putting extra in intent-->2");
returnIntent.putExtra("2",hashMap.get("two"));
}
if (hashMap.containsKey("three")){
Log.d(TAG,"Submit Button clicked and now putting extra in intent-->3");
returnIntent.putExtra("3",hashMap.get("three"));
}
setResult(Activity.RESULT_OK, returnIntent);
finish();
onActivityResult code in the activity with the button:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG,"onAcitivtyResult"); // <--- NOT EVEN THIS GETS CALLED when returning
if (//uninmportant stuff){
//Other uninporant stuff
else if(requestCode == 33){
Log.d(TAG,"[Test Return Code returned]!");
Log.d(TAG,data.getExtras().toString());
Bundle bundle=data.getExtras();
List<ProductImage> productImageList=new ArrayList<>();
for (Integer i=1;i<=3;i++){
if ( bundle.getByteArray(i.toString())!=null){
ProductImage productImage=new ProductImage();
productImage.setImageBytes(bundle.getByteArray(i.toString()));
productImageList.add(productImage);
}
}
addProductViewModel.fillUpMutableLiveData(productImageList);
addProductViewModel.updateCurrentId(0);
}
}
Every picture you add, gets converted to a byte[] array, and gets stored as an extra in the result intent.
That is a really bad idea.
though no errors can be found in the logs
You are probably getting "FAILED BINDER TRANSACTION" warnings.
Any reason why this might be happening ?
My guess is that you are attempting to pass too much data via IPC. The Intent that you supply to setResult() is passed from your app to a core OS process, then back to your app and the caller of startActivityForResult(). At best, you can pass 1MB in an IPC transaction, and depending on what else is going on at the time, the limit may be substantially lower.
Either:
Have one activity, not two, by using fragments for the individual bits of UI and using a shared ViewModel to expose the results of one fragment to another; or
Carefully hold onto these images in a shared cache, so you are passing cache keys as extras, not byte[]; or
Find some other architecture that you like that does not involve putting large byte[] values into an Intent for use with startActivity(), startActivityForResult(), or setResult()

How to kill a specific Instance of a Activity?

I know we can keep only one instance by
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
but I need that my app can create maximum two instances of certain activity not more than that.
I am using a chained search feature like image below
When Instance3 of the activity1 is created i want to destroy:-
Instance1 of activity1
Instance1 of activity2
Tried to used this to kill a specific activity but activity have same Pid for all processes in an app
android.os.Process.killProcess( stack.getLast());
is there a way we can moderate which instances should be kept alive?
any help would be great thanks!
In my opinion this is the wrong architecture. For chained search you should only ever have a single instance of each Activity. You should flip between the different Activity instances by calling startActivity() and setting Intent.FLAG_ACTIVITY_REORDER_TO_FRONT in the Intent you use. Also add the data you want to display as "extras" in the Intent.
To be able to use the BACK button to back through the chain (no matter how long it is), each Activity should manage a stack that contains the data that it needs to recreate the page whenever the user backs into it. In onCreate() and in onNewIntent() the data (from the "extras") should be pushed onto the stack and displayed. You then override onBackPressed() and go back to the previous Activity by calling startActivity(), and setting Intent.FLAG_ACTIVITY_REORDER_TO_FRONT in the Intent you use. You also add an "extra" to the Intent that indicates the user wants to "go back". In onBackPressed() you should also discard the top element off the data stack of the Activity that is being left. This will ensure that the stack is correct when the user then backs into this Activity.
In onNewIntent() if the user just backed into the Activity, you just display the data that is already on top of the stack of managed data.
In this way, you only ever have one instance of each Activity, the user can chain all day through the set of activities and the BACK button always works and you don't have to worry about running out of memory.
Trying to accomplish this using taskAffinity or Intent flags or similar will not work. Don't waste your time. It is also bad programming style.
I hope this is clear.
Basically you want to remove entries 1 and 2 from the backstack when you create the 5th one, but leave the 3rd and 4th. Unfortunately, the backstack doesn't work that way, you can only manipulate it from the top. You have the option of clearing all the activities except the last one if you set the flags FLAG_ACTIVITY_CLEAR_TASK and FLAG_ACTIVITY_NEW_TASK, you will have an empty task only with the just started activity.
If I were you, I wouldn't worry about memory consumption by the old activities though. As long as you stop the resource-consuming processes in your activities when they leave the screen and don't hold the references to them so they can be garbage collected, Android can manage the memory itself fine. What you should think about is whether it makes sense for the user to come back to the old activities if he presses back. If yes, then leave them, if no, then don't.
In case you want to kill all your activities on the back button press, there is an Activity.finishAffinity() method. Just override the onBackPressed method to call it.
You can use a little hack with EventBus or BroadcastReceiver or some other Bus. Check my following code for my idea. For this app, I will keep maximum 2 instances of MainActivity
public class MainActivity extends AppCompatActivity {
public static int count = 0;
int id;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
count ++;
id = getIntent().getIntExtra("ID", 0);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, MainActivity.class);
intent.putExtra("ID", count);
startActivity(intent);
}
});
}
#Override
protected void onStart() {
super.onStart();
EventBus.getDefault().register(this);
EventBus.getDefault().post(new Event(count));
}
#Subscribe
public void onEvent(Event event) {
if (id < (event.getID() - 2)) {//put your logic code to finish the activity here
Log.i("MainActivity", id + " is killed ");
finish();
}
}
#Override
protected void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
}
you can do this bro,
declare one Activity object like this
public static Activity factivity;
onCreate()
{
factivity = this;
}
now you can use that object to kill that specific activity on another activity like this:
onCreate()
{
FirstActivity.factivity.finish();
}

Android SaveInstanceState - Understanding

From this page of Android SDK
The default implementation takes care of most of the UI per-instance state for you by calling onSaveInstanceState() on each view in the hierarchy that has an id, and by saving the id of the currently focused view (all of which is restored by the default implementation of onRestoreInstanceState(Bundle)).
So is there a mechanism that automatically saves the Activity state without saving value from each element in the activity? I am confused about the above statement.
For an example, Activity A invoked Activity B. In Activity B, I have checboxes, Radio Buttons, etc. User select their choices and click Back button. I am showing the Activity At this point, I want to save the user selection. When user again comes back from Activity A to B, by clicking a button, I would like to see all selections persisted. One way I can think of is, setting the Intent Flag to bring the Activity to fore. But not a recommended method, I think.
So is there a default implementation to save the state, per the above text from SDK? Or may be I am interpreting it wrong?
onSaveInstanceState() and onRestoreInstanceState() are only explicitly called by Android when the Activity needs to be recreated, generally after a configuration change (ex. changing orientation). This doesn't cover the case when you have invoked a new instance of the Activity. When you press the back button, Activity B is destroyed, and you are creating a new instance of it the next time you start that Activity.
If you want to manually save the instance of an Activity, invoke Activity B via startActivityForResult(). Then, in Activity B, override the onDestroy() method, and call these lines of code:
#Override
protected void onDestroy() {
Bundle savedState = new Bundle();
onSaveInstanceState(savedState);
Intent data = new Intent();
data.putExtra("savedState", savedState);
setResult(RESULT_OK, data);
super.onDestroy();
}
In Activity A, override on onActivityResult and save the data:
Bundle activityBData;
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(resultCode == RESULT_OK) {
activityBData = data.getBundleExtra("saved_state");
}
}
Then, when starting Activity B again, call it like so:
Intent intent = new Intent(this, ActivityB.class);
if (activityBData != null) {
intent.putExtra("saved_state", activityBData);
}
startActivityForResult(intent, 0);
And lastly, in Activity B's onCreate method, restore the state:
if (savedInstanceState == null) {
Intent intent = getIntent();
Bundle savedState = intent.getBundleExtra("saved_state");
onRestoreInstanceState(savedState);
}
As per the documentation it will bring your activity forward when you start again ,if it is not killed .but to get all other views state back you need to store them in bundle in onSaveInstanceState() and set it again in onRestoreInstanceState().The default implementation works only for your activity not for your subviews in your activity
when an app loses focus to another app onSaveInstanceState() is called but when you navigate back to your app onRestoreInstanceState() may not be called. i.e. if your activity was NOT killed during the period when other activity was in front onRestoreInstanceState() will NOT be called because your activity is pretty much "alive".
All in all, as stated in the documentation for onRestoreInstanceState():
Most implementations will simply use onCreate(Bundle) to restore their
state, but it is sometimes convenient to do it here after all of the
initialization has been done or to allow subclasses to decide whether
to use your default implementation. The default implementation of this
method performs a restore of any view state that had previously been
frozen by onSaveInstanceState(Bundle).
For ex: From B you call startActivity(A). Then from A you call finish() to get back to B. In that case Your first activity, B will not have been destroyed, and neither onCreate() nor onRestoreInstanceState() will be called. These methods are only called when needed, that is when an activity has been destroyed and needs to be recreated by the system.
Use the SharedPreferences mechanism. Check out the documentation:
https://developer.android.com/reference/android/content/SharedPreferences
An example of implementation:
https://www.tutorialspoint.com/android/android_shared_preferences.htm
Also you can make use of PreferenceFragment to make this task easy.

popping a known number of activities from stack

in the process of building an app, I have an Activity lets call it A, and i open in multiple times, as A(1) -> A(2) -> A(3) -> ... -> A(m) -> ... -> A(n).
In the app I want to pop all the way back to A(m), is there a way to pop activities all the way back to a chosen one?
i already saw a few answers that had the FLAG_ACTIVITY_CLEAR_TOP but it seems to me like that is only to clear the whole stack and go to the first element in the stack and that is not what I'm trying to get.
update:
what I'm trying to achieve here is as follows: I have a list of questions, and the user can choose a different answer for each one.
once he chooses, the question and the answer go into a list of history, and another question is prompted. at any given point, the user can click a history button and go back to the question he chooses.
all the Q&A activities are the same, so going back to a question means popping the stack until getting there.
If I understand you correctly you are saying that you have N number of the SAME activity and wish to go back to some arbitrary starting point activity in the stack?
I am not totally clear on whether you know ahead of time that all activities forward of your "sticky" activity should be finished or if this is determined somewhere along the way
I don't know of any arbitrary way of popping N activities off the stack when they are duplicate activities but you could roll your own, such as (not finished code)
If you know ahead of time that A(M) will be sticky before it launches the next activity(s) you could just tell each forward activity that it needs to kill itself once it launches it's new task by passing it a flag then when the last activity in the chain ends you are back to A(M)
in A(...)
startSelf()
{
Intent I = new Intent(this,MyActivity.class);
if (bFinishSelf || bFinishForward)
I.putExtra("finishself", 1);
if (Finishelf)
finish();
startActivity(I);
in ... all A(...) onCreate( ...
Bundle b = getIntent().getExtras();
if(b !=null && extras.getInt("finishself");
bFinishSelf = true;
Then when you start a new activity check if the finishself flag is set and if so call finish() after starting new activity ...
If on the other hand A(z) is the one that determines you need to return to A(...) then I guess in A(z) you could tell the calling parent(s) that they need to kill themselves until reaching some ID contained in data ...
A(M)
startActivityForResult(...)
A(Z) somehow you determine that A(M) should be the sticky activity ...
finshAndStopAt(int ID) {
Intent I = new Intent();
I.putExtra("finish",ID);
if (getParent() == null) {
setResult(Activity.RESULT_OK, I);
} else {
getParent().setResult(Activity.RESULT_OK, I);
}
finish();
}
All A(z ... m) (parent's) would monitor for the return data
#Override void onActivityResult(int requestCode, int resultCode, Intent data) {
// If we are asked to finish ourselves, pass it on ...
if (null != data && data.getInt("finish",stopAt) != myID) {
if (getParent() == null) {
setResult(Activity.RESULT_OK, data);
} else {
getParent().setResult(Activity.RESULT_OK, data);
finish();
}
// else we can hang around and are the sticky activity
}
It's a bit of pain and maybe someone knows of an easier method
As a generic response, I would say to keep popping until the Activity returned by the pop is the one you want, then maybe push it back (if you need to).
Have you considered using Fragments? In this way you can use an instance of the FragmentManager class to handle the fragments history that you have generated through methods of the class FragmentTransaction. You can also tag your transactions and then pop all back stack states up to a transaction with a given name.
I don't know if there is a similar way to do this with Activities.
Take a look here:
http://developer.android.com/reference/android/support/v4/app/FragmentManager.html
http://developer.android.com/reference/android/support/v4/app/FragmentTransaction.html
If you use the support library you don't have to worry about you target SDK.

android when returning to activity it is destroyed and not resumed

I am building a android application with two Activities utilizing the Action Bar (https://github.com/johannilsson/android-actionbar as i am targeting Android 2.2).
It has a number of activities. There is a "Home" Activity, called Feed, and another activity called "Settings".
The problem i am having is that using the createIntent function that is given in the sample for the action bar i am using, the Activity still get destroyed rather than resumed when the user taps the home button to return to the Feed activity.
With a bit of debugging i found that it is getting destroyed, not when the Activity is first paused and stopped, but when the request for it to resume happens.
public static Intent createIntent(Context context)
{
Intent i = new Intent(context, Feed.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
return i;
}
This is a major annoyance, and considerably slows down the application as it has to reload its data for the feed (which is cached, but its not instant to get the data reloaded).
So how can i avoid this behavior? and why is this happening, as i believe the extra flag there should stop this behavior.
You want to use:
FLAG_ACTIVITY_REORDER_TO_FRONT
Quote from Android docs:
If set in an Intent passed to Context.startActivity(), this flag will
cause the launched activity to be brought to the front of its task's
history stack if it is already running.
For example, consider a task consisting of four activities: A, B, C,
D. If D calls startActivity() with an Intent that resolves to the
component of activity B, then B will be brought to the front of the
history stack, with this resulting order: A, C, D, B. This flag will
be ignored if FLAG_ACTIVITY_CLEAR_TOP is also specified.
public static Intent createIntent(Context context)
{
Intent i = new Intent(context, Feed.class);
i.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
return i;
}
Depending on which class is launching what, you might be having an issue where a new instance of your activity is being launched and the old one is being destroyed. This is what the documentation says about FLAG_ACTIVITY_CLEAR_TOP:
If set, and the activity being launched is already running in the current task, then instead of launching a new instance of that activity, all of the other activities on top of it will be closed and this Intent will be delivered to the (now on top) old activity as a new Intent.
Notice that it says "if the activity being launched is already running in the current task..." So I'm not sure, but this would make sense if your debugging has shown you that the activity is also being destroyed.
When debugging, are you looking at the "id" numbers for your class objects? Set a breakpoint in both the onCreate() and onDestroy() methods of your class and look at the id of the class inside each one. If they are different, then you know you have two different instances of your activity.
As a side note/question, how are you "targeting" 2.2 if the ActionBar wasn't made available until 3.0?

Categories

Resources