I'm stuck between a rock and a hard place. I am developing an application that shows a welcome page and asks a user to either register or sign in. A user can go to and from registration/sign in activity to main activity (welcome page) by up caret button and a phone's back button.
But once a user logs in or creates an account, I'd like both the main activity (welcome page) and previous activity (could either be login activity or register activity) to be removed from the application's stack so when a user taps a back button, they'd exit the application instead of going back to the activity they came from (either login or registration).
I can't set the android:noHistory="true" on registration and sign in activity in AndroidManifest as it'd also mean exiting from the application from that particular activity instead of going back to welcome page.
So, how can I dynamically set the noHistory flag on these activities upon reaching a particular activity? Or maybe there's something wrong with the application flow that I have in mind.
Appreciate your help, thanks!
I'd suggest you close the undesired Activities by using a broadcast reciever. I'll outline the steps here:-
Create an Acitvity to be used as a super class for the dangling Acitivies. They will listen for a custom broadcast message and call finish() when receiving the message.
public class LoginFinisherActivity extends Activity {
private FinishReceiver finishReceiver;
public static final String ACTION_FINISH_LOGIN = "ACTION_FINISH_LOGIN";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
finishReceiver= new FinishReceiver();
registerReceiver(finishReceiver, new IntentFilter(ACTION_FINISH_LOGIN));
}
#Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(finishReceiver);
}
private final class FinishReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_FINISH_LOGIN))
finish();
}
}
}
Any class that you don't want left dangling should extend LoginFinisherActivity.
When you reach a point where you know you don't want those activities anymore, get rid of them by sending the broadcast (for example in the onCreate() of some other Activity): sendBroadcast(new Intent(LoginFinisherActivity.ACTION_FINISH_LOGIN));
To summarise: Essentially there are 2 classes of activities that are in you scenario - early ones that you want cleaned up - let's call them 'registration' activities and activities that you land at when you are done with the previous flow (this may just be a single activity in your app?) - let's call them the 'landing' activities. So all you need to do in have all of your 'registration' activities extend LoginFinisherActivity and all of your 'landing' activities to call sendBroadcast(new Intent(LoginFinisherActivity.ACTION_FINISH_LOGIN)); in their onCreate().
I hope this works for you!
Related
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();
}
I have 2 activities, a 4 digit pin style login Activity (MainActivity) and a content Activity.
This app stores private information and such that should not be able to be seen when resuming an activity, therefore i want the app to completely restart on the login activity each time it's launched, rather than picking up where it left off.
I've tried messing around within the content activity's onStop() and onResume methods, but these seem to be highly unreliable as sometimes when i have the onRestart setup to relaunch the login activity, it won't launch it at all, and calling for the login screen to be activated in the onStop() will prevent me from being able to finish up tasks in the background, such as saving data.
Is there anything i can add to the manifest file that will tell the app to restart from the login activity no matter what? One of the worst things that can happen when working on an app like this is that the information is accessible to someone other than the owner that wasn't forced to login..
What you need is to focus on two calls in your Content Activity:
onPause(): This is where you kill Content activity and remove it from the stack. You can do this easily:
#Override
public void onPause() {
super.onPause();
//Save your data here
finish(); //Kill Contect Activity.
}
onRestart(): This is where you redirect the user to the Main Activity.
#Override
public void onRestart() {
super.onRestart();
Intent i = new Intent(this, MainActivity.class);
startActivity(i);
finish(); //Kill Contect Activity.
}
This graph might help you in understanding the Activity lifecycle:
What you need is setting the flag android:clearTaskOnLaunch and android:excludeFromRecents for your activities in the Manifest.
I have an Android app made of 3 activities A, B and C:
A is the application itself (a regular Activity)
B is the settings screen (made with PreferenceActivity)
C is the about screen (a regular Activity)
The relationship between the activities is as follows:
A is the main activity
B's parent is A
C's parent is B
Activity A raises a notification when going to background, this is accomplished by calling NotificationManager's notify() inside A's onStop() method.
When the user launches the app it starts from activity A, then if the user presses the home button the notification will be raised as soon as the home screen is shown. Clicking on the notification will get the user back to the app.
Everything seems to work but now there's a behaviour I tend not to like:
When the user launches the app and then goes to settings (therefore showing activity A and then B) the notification is also raised (because A has gone to background in order to show B).
This is not desired: The notification should be raised only when the application goes to background regardless of the activity the user was looking at.
How to implement this proper behaviour?
Thanks in advance,
Marco
just create a boolean flag shouldNotify and set it to false when you are opening one of your other Activities. Without your code I can't tell you exactly how to implement it but here is an example to get the idea accross:
private boolean shouldNotify = true;
#Override
public void onStart(){
super.onStart();
shouldNotify = true;
}
#Override
public void onStop(){
super.onStop();
if(shouldNotify){
sendNotification();
}
}
// Where you need to put these next snippets depends on how you've made your Activity
/*
* Launching settings activity
*/
shouldNotify = false;
Intent settingsIntent = new Intent(this, YourSettingsActivity.class);
startActivity(settingsIntent);
You could set a boolean flag which is checked before raising the notification and set it to "false" before startig Activity B.
Of course this way you would have to do the same in Activity B and C for them to raise the notification, too.
Or, as hinted to before you could implement an own application class and fetch the callback there.
Firstly, I'll explain the situation I'm in with my applicaton before I get onto the main question I have. Firstly, I want to find out if the way that I am handling activities is a proper way of handling activities, as I feel it might well not be which in turn is causing my problems.
Currently I have 3 activities setup. Main.class, Login.class and Display.class. The Main activity doesn't have any UI associated with it at all, it launches other activities. So, the first activity that get's launched when the app is launched is the Main activity, and it goes and reads from a SharedPreferences store whether or not the user is logged in. If they aren't logged in, it will open up Login.class, if they are logged in, it will open up Display.class (both of which have UI's associated with them). It uses startActivityForResult().
Because of the way I decided to go around working with activities, I needed to override the back button on both Login.class and Display.class: otherwise, it will go back to the Main.class, and re-run the launch activity sequence, which will inturn re-launch the activity that was just running (therefore the back button is useless). So, I overrode the back button to send back an integer of -1 and then finish() the current activity. I've overridden the onActivityResult(int, int, Intent) for Main.class, and if it receives a -1, it will finish() as well, instead of trying to re-launch the activity.
Now, that all worked for how I wanted it, though I have a feeling that it's the completely wrong way to handle activities, and it's causing problems for me later down the track.
In my application, I decided I wanted to put an app widget and a notification in, both of which will open the app when clicked (via an intent that launches Main.class). I got it all working so that it would do that, though it seems as though starting a new Activity causes problems.
What happens is basically, if the application is already running, and someone pulls down the notification and clicks on it, it will open up a new activity of the same application. This is problematic, especially with the way I handle back buttons: When you open a new one, and press the back button, it will close the new one (as expected,) but will then take you back to the OLD one. You can then press the back button again and exit it, but obviously I don't want to release an app that needs the back button to be pressed twice. What is the best way to sort this problem out? I'm happy to completely change the way I handle activities if that's a major part of the problem.
Thanks,
Josh.
Actually how you set a -1 (which is fine) and then call finish() is perfectly acceptable. I forget the property values, but if you want only a single instance of an Activity there is a way to set that in the manifest ... Task affinity or one of those values - you'll have to look it up.
As an alternate, you can override the Application class and use your own to manage application state. Think of it as a singleton tracker for the main Activity .. "if it already exists use that one, otherwise create a new instance" kind of thing. When creating your main Activity set a reference in your newly extended Application class (make sure you null this out when main is shutting down), then check to see if a reference is available when onCreate() fires again ... if there is a reference already there use that instance of Main .. if not, proceed as normal and set it.
Food for thought ...
I have a feeling that I don't fully understand the depth of the problem, so please forgive me if I'm saying something that you already tried.
But couldn't you remove Main and launch Login from Display? I mean, makes more sense. That's what I do here all the time when I need accessory activities that must fill data for the main activity (which is obviously Display in your case). And when you return from login, you could do all the checks you need. You could allow, say, a "read-only" Display, you could provide a dialog warning...
You would save a lot of trouble and useless code indeed (most of the result/intent spaghetti).
A feasible approach is to have a class extending Application and a couple of Activitys bound to it. A private boolean logged value can determine whether the Display needs to call LogIn to foreground.
public class MyApp extends Application {
private static boolean logged = false;
private Activity logInActivity;
private Activity displayActivity;
public void onCreate () {
super.onCreate();
}
public void setDisplayActivity (Activity act) {
displayActivity = act;
}
public void setLogInActivity (Activity act) {
logInActivity = act;
}
public void finishActivities (Activity act) {
activity.finish();
}
public void setLogged (boolean logged) {
this.logged = logged;
}
public boolean isLogged () {
return logged;
}
public Activity getLoginActivity() {
return logInActivity;
}
public Activity getDisplayActivity() {
return displayActivity;
}
}
class Display extend Activity {
private MyApp app;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
app = (MyApp) getApplicationContext();
if (!app.isLogged()) {
// start LogInActivity. After log in, it will call app.setLogged (true);
}
else {
// continue with Display;
}
}
}
I've done qui a bit of reading and searching on SO, but can't find a way to clear the current activity stack. The context of my app is an activity started by a a background service / notification.
Imagine that my app allows you to organise a list of people. A few hours ago, you were viewing person X in the "View" activity, that's now the top of your stack. At some point in the future, the service triggers and I popup a new "Notify" activity for person Y. From there you can edit person Y's details.
When you finish this activity, it would be a confusing user experience to pop the stack and end up viewing person X. Ideally I'd like to go back to whatever the user was doing (email etc...), or at least to my app's home.
I tried starting "Notify" with FLAG_ACTIVTY_NEW_TASK but that doesn't seem to help: when the task finishes it simply goes back to the previous task. What I want seems to be Android 3's new FLAG_ACTIVITY_CLEAR_TASK, which doesn't exist in previous SDKs.
Does anyone have a suggestion to achieve that?
Just kill'em all!
You can achieve that by using BroadcastReceivers:
Create a BaseActivity like this:
public class BaseActivity extends GuiceActivity {
private KillReceiver mKillReceiver;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mKillReceiver = new KillReceiver();
registerReceiver(mKillReceiver,
IntentFilter.create("kill", "spartan!!!"));
}
#Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(mKillReceiver);
}
private final class KillReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
finish();
}
}
}
Make your activities extend that BaseActivity.
Whenever you want to clear the stack:
Intent intent = new Intent("kill");
intent.setType("spartan!!!");
sendBroadcast(intent);
If you already have an instance of your app's home running in the stack, when activity Y finishes you could start your app's home (using startActivity()) with the flag FLAG_ACTIVITY_CLEAR_TOP. As it's already on the stack, instead of creating a new instance of it, this would bring you back to your app's home and clear the stack on top of it.
Take a ArrayList and save all activities objects into arraylist in oncreate() of every activity. Whenever you want to finish particular activity ,just retrieve that activity instance from arraylist and finish that.