how to check if activity is still in the stack? - android

what is the better way to check if the activity is still in the stack in order to call it back ?
Intent i = new Intent(getApplicationContext(),MyClass.class);
startActivity(i);

I am surprised how unpopular this (kind of) question(s) is.
Let me start from the solution first:
Since ActivityManager.getRunningTasks is deprecated since API 21,
We have to find another way to get what activities are in the backstack. And I realized that we can actually implement our own "stack"!
I declared an ArrayList in MyOwnApplication:
private ArrayList<Class> runningActivities = new ArrayList<>();
And added public methods to access and modify this list:
public void addThisActivityToRunningActivityies (Class cls) {
if (!runningActivities.contains(cls)) runningActivities.add(cls);
}
public void removeThisActivityFromRunningActivities (Class cls) {
if (runningActivities.contains(cls)) runningActivities.remove(cls);
}
public boolean isActivityInBackStack (Class cls) {
return runningActivities.contains(cls);
}
In a BaseActivity where all activities extend it:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyOwnApplication)getApplication()).addThisActivityToRunningActivityies(this.getClass());
}
#Override
protected void onDestroy() {
super.onDestroy();
((MyOwnApplication)getApplication()).removeThisActivityFromRunningActivities(this.getClass());
}
And then you can easily use isActivityInBackStack to check.
WHY IS THIS NECESSARY?
Yes, of course, most cases can be done by using Intent Flags and proper navigation.
But there is such a use case, which I think should be common, that I don't find a solution simply by using intent flags.
Suppose I have an application that has a navigation drawer in almost every activity.
I navigate from MainActivity to ActivityA, and then created ChildActivityB from ActivityA. Please note that ActivityA is not parent of ChildActivityB since ChildActivityB can be opened from other activities such as notification.
Note that, ChildActivityB also has a drawer. I can navigate to ActivityA through drawer, instead of pressing up or back button. Now, imagine you loop through such process: Activity A -> ChildActivity B -> Drawer -> Activity A -> ChildActivityB -> Drawer -> Activity A .....
Infinite activities will be created in the backstack.
To fix such behavior, we need to use Intent Flags:
(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
So far so good.
However, I have custom activity transition animations by using overridePendingTransition(). I noticed that if I put the above intent flags together with overridePendingTransition(), there will be a glitch in animation because the activity is destroyed at the middle of the animation, due to the flag Intent.FLAG_ACTIVITY_CLEAR_TOP.
Now, if I am able to detect whether ActivityA is in the backstack or not, the behavior will be perfect:
private void navigateToDrawerItems(Class cls) {
drawerLayout.closeDrawer(GravityCompat.END);
Intent intent = new Intent(this, cls);
if (((MyOwnApplication)getApplication()).isActivityInBackStack(cls)) {
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
} else {
startActivity(intent);
overridePendingTransition(R.anim.slide_right_in, R.anim.slide_left_out);
}
}

You can toggle global variable as indicator inside onCreate() and onDestory() of specific class, OR inside onActivityCreated() and onActivityDestroyed() of ActivityLifecycleCallbacks.
e.g.:
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
#Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
if (activity instanceof YourActivity) {
myGlobalData.setActExist(true);
}
}
#Override
public void onActivityDestroyed(Activity activity) {
if (activity instanceof YourActivity) {
myGlobalData.setActExist(false);
}
}
});

Look at the ActivityManager API
To get an instance of the ActivityManager use this code:
ActivityManager mngr = (ActivityManager) getSystemService( ACTIVITY_SERVICE );

Related

How to remove specific activity from the stack

Lets put we 3 activates that may follow the stack A->B->C
How can I close A and keep B->C, but only if B opens C?
C is not always opened, but if it gets opened only then A should be removed, leaving the stack as B->C. Otherwise will remain as A->B.
I don't see the possibility of using finish() in A, as it should be closed from B when opening C.
By the way A is a single instance with its own affinity.
Write an abstract BaseActivity class and implement a LocalBroadcastManager in it and register a BroadcastReceiver. Note that we have also introduced an abstract onFinishReceived method.
public abstract class BaseActivity extends AppCompatActivity {
private LocalBroadcastManager mLBM;
private final BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
onFinishReceived(intent.getStringExtra("activity"));
}
}
protected void onCreate(#Nullable Bundle savedInstanceState) {
...
mLBM = LocalBroadcastManager.getInstance(this);
IntentFilter filter = new IntentFilter();
filter.addAction(...);
mLBM.registerReceiver(receiver, filter);
}
protected void onDestroy() {
mLBM.unregisterReceiver(receiver);
mLBM = null;
}
abstract void onFinishReceived(String activity);
}
Then, extend all A, B and C Activities from the BaseActivity and override the onFinishReceived() method.
public class A extends BaseActivity {
void onFinishReceived(String activity) {
if (activity.equals("A") { // or "B" or "C"
finish();
}
}
}
}
Now, whenever you want to finish a specific activity use,
LocalBroadcastManager.getInstance(context)
.sendBroadcast(new Intent()
.setAction(...)
.putExtra("activity", "A") // or "B" or "C"
);
LocalBroadcastManager is now deprecated. It's better if you can use something like RxJava. The idea is to use an asynchronous and event-based mechanism rather than going for a simple ugly solution like storing the Activity in a static variable.

How to finish specific activities not all activities

In my android application suppose several activities are there
if using intent I go to other activities like this
[Activity A]->[activity B]->[Activity C]->[Activity-D]->[Activity N]
and now when am on activity N when I pressed button then I want to go to Activity B and want to destroy Activity C And Activity D but Activity A should not destroy. I also searched in various posts but I didn't get exactly the same solution.
Any help will be appriciated
In ActivityN, to return to ActivityB use this:
Intent intent = new Intent(this, ActivityB.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
Using the flag CLEAR_TOP will finish all activities in the stack that are on top of ActivityB. In the example you gave, this will finish ActivityN, ActivityD and ActivityC. Using the flag SINGLE_TOP will make sure that this will return control to the existing instance of ActivityB (ie: it won't create a new instance of ActivityB).
In Your Activity C do like this
public static ActivityC instance = null;
public class ActivityC extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
instance = this;
}
}
And in your Activity D do like this
public static ActivityD instance = null;
public class ActivityD extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
instance = this;
}
}
Finally in your Activity N. Do Something like this
public class ActivityN extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button yourButton= (Button) findViewById(R.id.yourButton);
yourButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ActivityC.instance.finish();
Activityd.instance.finish();
finish();
}
});
}
}
Here's my approach.
From Activity A, don't just start the Activity B, call startActivityForResult() method. Do this for all subsequent calls.
Now, when you press the button from Activity N, set the result for a custom value and call the finish() method for Activity N. Now you should hit the onActivityResult method on your Activity D. Now you can check whether the result was you pressing the button. Depending on your result, keep on setting the result and subsequently calling finish() on each Activity.
This should technically work.
Try this code:
//Activity A
Intent i = new Intent(getApplicationContext,ActvityB.class);
startActivity(i);
//Activity B
Intent i = new Intent(getApplicationContext,ActvityC.class);
startActivity(i);
//Activity C
Intent i = new Intent(getApplicationContext,ActvityC.class);
startActivity(i);
finish();
// finish here actvity which you want to finish
//Try this second way:
In your first activity, declare one Activity object like this,
public static Activity fa;
onCreate()
{
fa = this;
}
now use that object in another Activity to finish first-activity like this,
onCreate()
{
FirstActivity.fa.finish();
}
EDIT : Use startActivityForResult() instead of startActivity()
So depending on the result you can change the behavour.
Say for example When you wanted to go to ActivityB just return some flag in the INTENT. When it will be caught in Activity D and C in onActivityResult(), finish them and you will be finally on B.
Flag Intent.FLAG_ACTIVITY_CLEAR_TOP may solve your problem:
http://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_CLEAR_TOP
You can start ActivityC, ActivityD, ActivityN with the same request code passed to startForResult(requestCode)
And then at ActivityN, use finishActivity(int requestCode).
Documentation for finishActivity(int requestCode)
Force finish another activity that you had previously started with startActivityForResult.
Params:
requestCode – The request code of the activity that you had given to startActivityForResult().
If there are multiple activities started with this request code, they will all be finished.

Find out whether the current activity will be task root eventually, after pending finishing activities have disappeared

If FirstActivity is the root of the task, and it finishes itself and launches SecondActivity, then calling isTaskRoot() in SecondActivity immediately will return false, because the FirstActivity's finishing happens asynchronously and thus isn't done yet. Waiting for a second and then calling isTaskRoot() returns true.
public class FirstActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
finish();
startActivity(new Intent(this, SecondActivity.class));
}
}
public class SecondActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
#Override
protected void onResume() {
super.onResume();
((TextView)findViewById(R.id.tv1))
.setText("isTaskRoot() in onResume(): " + isTaskRoot());
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
((TextView)findViewById(R.id.tv2))
.setText("isTaskRoot() after 1s: " + isTaskRoot());
}
}, 1000);
}
}
Is there a way to …
(optimally) find out whether the activity will be the task root eventually, or,
(better than nothing) get some sort of notification/callback once the task is in its "final" state and thus isTaskRoot() will return the "truth"?
I've had a similar problem and I wanted tight control over exactly who the root activity is. In my case, the root could only be one of my own activities (not 3rd party ones), so I was able to use the following approach:
I extended the Application class, added a weak reference to an activity called currentRootActivity and added synchronized getter and setter.
Then I managed this state by myself when activities were created / destroyed. My use case was a little special because I was looking to replace one root with another, so I knew exactly where to reset my new state variable, but I'm pretty sure you can do the same.
I was even able to add this state logic in a shared base class for all of my activities. So this wasn't as disgusting as it sounds :)
As mentioned in the comments, the activity method isFinishing might also come in handy.
Try:
Intent intent = new Intent(this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
I expect that this will make SecondActivity the root activity.

Finish Activity() from a separate myJavaClass.java

I have tried almost all the solutions from SO but no success :(.
I have a simple myJavaClass.java with a couple of functions.
One of the functions in myJavaClass : startActivity() starts MyCustomActivity
public startActivity(Context context)
{
Intent intent = new Intent(context, MyCustomActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent);
}
This launches MyCustomActivity() as expected.
Now I have another function in myJavaClass.java to close/finish MyCustomActivity but it is not able to do so!
I have tried
Making MyCustomActivity SingleTop in manifest and creating the activity via an intent as above
Passing an activity instance to "this" in onCreate() of MyCustomActivity and calling MyCustomActivity.activity.finish() from myJava.class but that doesnt work as well
Please help me. I have been stuck here for hours now. I know the solution is very simple and conceptual but I am a newbie. Just building Java/Android concepts!
EDIT
MyCustomActivity
public Activity activity;
OnCreate()
{
...
this = activity;
}
MyJavaClass
public closeActivity(Context context)
{
Activity customActivity = MyCustomActivity.activity;
customActivity.finish();
}
I think that what you are trying to do is fundamentally bad. For a start, outside of the Activity code, there are no guarantees that the activity still exists - the memory manager may have cleaned it up, the user may have pressed Back etc. Think of Activities as independent entities - you can start them, and you can optionally get a result back when they finish what they're doing, but that's it.
Think about whether you really have to programmatically close the activity from outside it - I'd say this is an unusual design, but there are circumstances where it may be appropriate.
If so, what I think you want is a publish/subscribe system whereby MyCustomActivity can register a listener with MyJavaClass, and then receive a callback whereupon it can 'finish' itself.
public Activity activity implements FinishListener
{
public void onCreate(...)
{
//where does MyJavaClass come from? see in a minute
MyJavaClass myjava = getMyJavaclass();
myJava.addFinishListener( this );
}
public void onFinishCallback()
{
this.finish();
}
}
and
public class MyJavaClass
{
private List<FinishListener> finishListeners = ...;
public void addFinishListener( FinishListener fl )
{
this.finishListeners.add(fl);
}
public closeActivity(Context context)
{
for ( FinishListener fl : finishListeners )
{
fl.onFinishCallback();
}
}
}
and
public interface FinishListener
{
void onFinishCallback();
}
Now the only remaining issue is how to get MyJavaClass from the Activity. That's up to you - you may already know how, you may be able to put it in your Application implementation, it could be a singleton (bad), the listeners could be static (bad) or various other options.
Oh, and don't forget to remove the listener again in the Activity's onDestroy() method!
Just try this....
public closeActivity(Activity _activity)
{
_activity.finish();
}
you can't finish activity from other class until you have the reference of instance of Activity in that class, give the reference in that class and call finish() method to stop the activity.
activity.finish();

Notification opens activity, press back button, prevent opening back stack activity?

This looks very similar to my previous question because it's some sort of follow up. I was not very happy with the only solution given; also, the solution was for a problem slightly different from this one. So let me try to explain the problem again...
A notification is created at boot (with a BroadcastReceiver).
My app main activity is opened and the home button is pressed (the activity will be sent to the back stack).
I pull down the status bar and press on the notification previously created at boot.
That will start some activity, different from the main one.
I press the back button and the main activity is displayed.
This is not very different from my previous question... The thing is, "main activity" was just an example. I could have opened the app main activity and then opened the about activity through a menu option and pressed the home button. The back stack would now be MainActivity » AboutActivity. Which means that when the back button is pressed while in "some activity" (started by pressing the notification), we would be brought to the top of the back stack, that is, the about activity.
What basically want is to prevent any other activity to be opened when I press the back button while in "some activity" (again, started by pressing the notification). I want to be brought exactly where I was, that could be the desktop or some other app's activity, but not my app's MainActivity nor AboutAcitivity cause that's not where I was, those were in the back stack, "sleeping" in the background.
I have come up with a solution, but I don't think it's very elegant and I was looking for something more, well, elegant... If you have any other suggestion, please, let me know.
Anyway, this is my proposed solution:
// I use this class for public static (or public static final) members and
// methods
public final class AppHelper {
public static final String KEY_RESUME_FROM_NOTIFICATION = "resumeFromNotification";
private static boolean sResumeFromNotification = false;
public static boolean getResumeFromNotification() {
return sResumeFromNotification;
}
public static void setResumeFromNotification(boolean resumeFromNotification) {
sResumeFromNotification = resumeFromNotification;
}
}
public class MainActivity extends ListActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
(...)
}
#Override
protected void onResume() {
super.onResume();
if(AppHelper.getResumeFromNotification()) {
AppHelper.setResumeFromNotification(false);
moveTaskToBack(true);
}
}
}
public class AboutActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
(...)
}
#Override
protected void onResume() {
super.onResume();
if(AppHelper.getResumeFromNotification()) {
AppHelper.setResumeFromNotification(false);
moveTaskToBack(true);
}
}
}
public class SomeActivity extends Activity {
// This will be called when the notification is pressed and the activity is
// not opened yet
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
(...)
extractIntentExtras(intent);
}
// This will be called if the activity is already opened and the
// notification is pressed
#Override
protected void onNewIntent(Intent intent) {
extractIntentExtras(intent);
super.onNewIntent(intent);
}
private void extractIntentExtras(Intent intent) {
Bundle bundleExtras = intent.getExtras();
if(bundleExtras != null) {
// These intent extras are set on the Intent that starts this activity
// when the notification is pressed
AppHelper.setResumeFromNotification(bundleExtras.getBoolean(
AppHelper.KEY_RESUME_FROM_NOTIFICATION));
mRowId = bundleExtras.getLong(AgendaNotesAdapter.KEY_ROW_ID);
populateNoteUpdateFields();
}
}
}
I don't know, but this solution doesn't look very elegant to me (but it works as I expect it) and I'm looking for alternatives or for strong opinions on my proposed solution as an acceptable and good solution. Thoughts?
After doing some more reading perhaps this is the combination of flags you need:
Intent intent = new Intent(mContext, SomeActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
startActivity(intent);
I think that should force your SomeActivity class to be launched in a completely new task.
When launching the Activity from the notification, you can control how the Activity you are about to open is put on the back stack, and what task it's associated with with Intent flags. You can try something like:
Intent intent = new Intent(mContext, SomeActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
If that doesn't work, try setting a few of the other flags until you get the desired behavior.
Do you ever want your MainActivity to stay in history? If not then my simple, crude solution is to finish the MainActivity when it is paused.
(Call this in your MainActivity)
#Override
public void onPause() {
finish();
}
This will ensure that your MainActivity is removed from history when you navigate away from it, and will never appear when the back button is pressed.
This could be used for AboutActivity as well.

Categories

Resources