One application with two independent activities - android

I am developing simple application with only two activities.
Activity C configures application
Activity A for interaction with user when some event occur
It is not possible for user to navigate from the one to the other - this is why I call them independent activities. Further more activity A is being invoked only form event, there is no way for user to do it manually.
Problem. Let's assume that application is properly configured. Some event occurs in the system, so application A is being shown to the user. The user interact with it and activity goes to background. Then the user decides to launch configuration activity C. Activity C is shown to the user. The user uses back button to "close" activity, but instead of android launcher or desktop the user is being shown activity A (taken from history).
Similar scenario might happen the other way. C is being used by user, then taken to background. Some event shows activity A and user using back button goes to C instead of closing activity A.
I have solved the problem, but the solution is pretty dirty. Is there any clean or standard way of solving such problem?
Part of my solution includes what was suggested in one answer:
snippet from AndroidManifest.xml:
<activity
android:name=".C"
android:clearTaskOnLaunch="true"
android:excludeFromRecents="false"
android:launchMode="singleTask"
...
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:exported="false"
android:name=".A"
android:excludeFromRecents="true"
android:noHistory="true"
android:launchMode="singleInstance"
android:clearTaskOnLaunch="true"
....
>
</activity>
snippet from activity A:
public boolean onKeyUp(final int p_keyCode, final KeyEvent p_event) {
switch(p_keyCode) {
case KeyEvent.KEYCODE_ENDCALL:
case KeyEvent.KEYCODE_HOME:
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_POWER:
this.finish();
break;
....
}
return super.onKeyUp(p_keyCode, p_event);
}
snipped from event handler:
public class H extends BroadcastReceiver {
...
Intent intent = new Intent(p_context, A.class);
intent.addFlags(Intent.FLAG_FROM_BACKGROUND);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
p_context.startActivity(intent);
...
}
It works for my application. However I want application (activity C) to appear in Recent application. But once activity A is invoked application is removed from Recent.

I don't know if this is the cleanest way to do this, but you can override the void onBackPressed() activity method. This way you can mannually move your activity to the background, like this, and prevent the previous activity from popping in:
public void onBackPressed () {
moveTaskToBack (true);
}
Edit: Turns out there's a better way to do this:
Open your AndroidManifest.xml, and inside each declaration put the following: `android:noHistory="true"``. Doing so will tell Android that your activity does not leave a history, and therefore, when the user hits back Android will quit the application, since there's no other activity for it to return to.

Related

Android prevent back button from closing the app

In my app, I want to prevent the user from going back to login activity after logging in, but I don't want to close the app. For example if the previous activity is login activity, I don't want to do anything when the user presses back, just stay in the current activity/fragment.
add finish(); in login activity and add onBackPressed in your next activity
#Override
public void onBackPressed() {
}
Add android:noHistory="true" in the manifest of your LoginActivity
<activity android:name=".LoginActivity" android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
The correct way to do this is to call finish() in the Login activity when it completes and launches the next activity. For example:
Intent intent = new Intent(LoginActivity.this, NextActivity.class);
startActivity(intent);
finish();
This would prevent the user from going back from NextActivity to LoginActivity since calling finish() destroys LoginActivity and removes it from the back stack.
There is no need to remove onBackPressed from NextActivity, and that may have unintended consequences (for example, if they navigate from Login -> Next -> Other -> Next then click back, they would expect to go back to Other). Disabling onBackPressed in Next would prevent that.

Backstack not cleared when notification clicked

I'm using Firebase Cloud Messenger (FCM) to push notifications to my app.
The notifications are received when the app is in the background so onMessageReceived is not triggered as the notification doesn't have a payload (data). Everything is fine with that but it means I can't create my own notification as everything is automatically handled by System tray.
When I click the notification I expect the entire backstack to be cleared and the app to restart from scratch. Basically I want the opposite of this post.
This is supposed to be the default behaviour.
However, when I click on the notification, if the app was already opened, the app restarts from the launcher but on top of the existing backstack.
For instance if you have:
HomeScreen -> Page1
when the notification is clicked, you now have in the stack:
HomeScreen -> Page1 -> HomeScreen
when it's supposed to only be:
HomeScreen
My launcher is an Activity only displayed when the app starts so I don't want it to be kept in the backstack. I turns out this this why I get this issue. So basically if the Launcher Activity calls finish() on itself and/or has noHistory="true" set in the Manifest, the backstack is not cleared when the notification is clicked.
How can I solve this issue?
I found a solution. The idea is to create a new LauncherActivity in charge of launching the existing one and clearing the backstack in the process.
There are probably other ways to do that but I wanted to keep the original Launcher with noHistory="true" as otherwise I have issue with the transition animation with the next Activity if I implemented the below solution directly to it.
The new Launcher is called StartActivity
In the Manifest:
<activity
android:name=".StartActivity"
android:screenOrientation="portrait"
android:theme="#style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
The Activity:
public class StartActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, LauncherActivity.class);
// Add the flags to clear the stack
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
// Start the intent with a defined transition animation. The animation is not
// required but it make the transition seamless.
startActivity(intent, getFadeInOutAnimation(this));
// Necessary for the app not to crash. Basically just a FrameLayout
setContentView(R.layout.activity_start);
}
public static Bundle getFadeInOutAnimation(Context context) {
// Allows us to display a fading animation as transition between the activities.
// The animation can be whatever you want
return ActivityOptions.makeCustomAnimation(context,
R.anim.fade_in, R.anim.fade_out).toBundle();
}
}

Beginning an application while no starting activity set

I would like to launch my application and check the connectivity state in application's onCreate method then decide which activity to start! I know that I could finish() a default MAIN/LAUNCHER activity before to setLayout while starting another if it's relevant but that seems messy to me!
So, I would like to know if it is possible to start an application whose doesn't manifest an activity with action.MAIN / category.LAUNCHER? I tried this way but it doesn't work! I mean the application seems to start but no activity is shown!
(This is not a sample from my real code, I'm not at home right now! Some arguments and stuff may be missing but I think you get the point!)
public class MyApp extends Application {
onCreate() {
Intent intent = new Intent(this, MyActivity.class);
intent.setFlags(Intent.NEW_TASK);
this.startActivity(intent);
}
}
Also, the first activity of my application may be an AlertDialog and I'm wondering if I can start one while no activity is started or if I'm forced to set an activity's theme with #android:style/Theme.Dialog?
I tried the same as for the above example but same result : logcat saying application alive while no printing at all...
Tell me if I'm not clear enough and in which way! I'm not an english speaker and I'm not used to ask in forums!
You will have to go this way:
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.some_empty_or_loading_view); //optional probably, not sure
//TODO: check whatever you want
if(condition) {
startActivity(this, SomeActivity.class);
} else {
startActivity(this, AnotherActivity.class);
}
finish();
}
}
Specify Your App's Launcher Activity
When the user selects your app icon from the Home screen, the system calls the onCreate() method for the Activity in your app that you've declared to be the "launcher" (or "main") activity. This is the activity that serves as the main entry point to your app's user interface.
You can define which activity to use as the main activity in the Android manifest file, AndroidManifest.xml, which is at the root of your project directory.
The main activity for your app must be declared in the manifest with an that includes the MAIN action and LAUNCHER category. For example:
<activity android:name=".MainActivity" android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Note: When you create a new Android project with the Android SDK tools, the default project files include an Activity class that's declared in the manifest with this filter.
If either the MAIN action or LAUNCHER category are not declared for one of your activities, then your app icon will not appear in the Home screen's list of app

Android onCreate called on Second Activity

Hi I have 2 activities,
Activity A and Activity B,
So its Activity A and on a Button click I go to Activity B.
Now I press home button and go and do somany other things. Because of memory issue the task is closed by android. Now when I try to open it again it starts from Activity B. Is this expected?
If yes is there a way to prevent it?
Below is the part of AndroidManifest where both activities are defined
Activity A = MenuActivity, Activity B = AndroidLauncher
<activity
android:name=".AndroidLauncher"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="#string/app_name"
android:finishOnTaskLaunch="true"
android:screenOrientation="portrait" >
</activity>
<activity
android:name=".MenuActivity"
android:clearTaskOnLaunch="true"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Also I am putting the code of onCreate of Activity B
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game_view);
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
int gameType = getIntent().getIntExtra("GAME_TYPE", 0);
GDXtoAndroidInterface.sharedInstance().activity = this;
TurnBasedHelper.sharedInstance().listener = this;
FrameLayout lg=(FrameLayout)findViewById(R.id.layout);
lg.addView(initializeForView(new LetterPress(GDXtoAndroidInterface.sharedInstance(),gameType), config));
busyDialog = new ProgressDialog(this);
busyDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
busyDialog.setCanceledOnTouchOutside(false);
busyDialog.setCancelable(false);
showBusy("Setting up..");
Log.d("MSG", "on create called launcher");
}
Also the reason why I need this is Activity A is like a login screen and Activity B is based on it. Activity B cannot work alone. User has to move from Activity A to Activity B
Thanks
when I try to open it again it starts from Activity B. Is this expected?
yes, this is the expected behavior.
when you press the home button (not the "back button") the entire task is sent by default to background, and when re-launching it (from recent tasks screen or the app icon at the home screen) it coming back by default to foreground with the same stack state as it was. same behavior applies also if your app was killed by the system while it was in background to preserve memory. the operating system will store the state of each activity in the stack, an restore it when it need to re-created (see this post)
you can think of the home button in relation to Activity like the relation in Windows OS terms as the "minimize" window button, and back button would be like the "close window" (x) button.
there a way to prevent it?
assuming you have good reason to do so and break this consistence expected behavior, you can use all kind of combinations between activity launch mode and intent flags that will give you the behavior you want..

Android: bug in launchMode="singleTask"? -> activity stack not preserved

My main activity A has as set android:launchMode="singleTask" in the manifest. Now, whenever I start another activity from there, e.g. B and press the HOME BUTTON on the phone to return to the home screen and then again go back to my app, either via pressing the app's button or pressing the HOME BUTTONlong to show my most recent apps it doesn't preserve my activity stack and returns straight to A instead of the expected activity B.
Here the two behaviors:
Expected: A > B > HOME > B
Actual: A > B > HOME > A (bad!)
Is there a setting I'm missing or is this a bug? If the latter, is there a workaround for this until the bug is fixed?
FYI: This question has already been discussed here. However, it doesn't seem that there is any real solution to this, yet.
This is not a bug. When an existing singleTask activity is launched, all other activities above it in the stack will be destroyed.
When you press HOME and launch the activity again, ActivityManger calls an intent
{act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]flag=FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_RESET_IF_NEEDED cmp=A}
So the result is A > B > HOME > A.
It's different when A's launchMode is "Standard". The task which contains A will come to the foreground and keep the state the same as before.
You can create a "Standard" activity eg. C as the launcher and startActivity(A) in the onCreate method of C
OR
Just remove the launchMode="singleTask" and set FLAG_ACTIVITY_CLEAR_TOP|FLAG_ACTIVITY_SINGLE_TOP flag whenever call an intent to A
From http://developer.android.com/guide/topics/manifest/activity-element.html on singleTask
The system creates the activity at the root of a new task and routes the intent to it. However, if an instance of the activity already exists, the system routes the intent to existing instance through a call to its onNewIntent() method, rather than creating a new one.
This means when the action.MAIN and category.LAUNCHER flags targets your application from the Launcher, the system would rather route the intent to the existing ActivityA as opposed to creating a new task and setting a new ActivityA as the root. It would rather tear down all activities above existing task ActivityA lives in, and invoke it's onNewIntent().
If you want to capture both the behavior of singleTop and singleTask, create a separate "delegate" activity named SingleTaskActivity with the singleTask launchMode which simply invokes the singleTop activity in its onCreate() and then finishes itself. The singleTop activity would still have the MAIN/LAUNCHER intent-filters to continue acting as the application's main Launcher activity, but when other activities desire calling this singleTop activity it must instead invoke the SingleTaskActivity as to preserve the singleTask behavior. The intent being passed to the singleTask activity should also be carried over to the singleTop Activity, so something like the following has worked for me since I wanted to have both singleTask and singleTop launch modes.
<activity android:name=".activities.SingleTaskActivity"
android:launchMode="singleTask"
android:noHistory="true"/>
public class SingleTaskActivity extends Activity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
intent.setClass(this, SingleTop.class);
startActivity(intent);
}
}
And your singleTop activity would continue having its singleTop launch mode.
<activity
android:name=".activities.SingleTopActivity"
android:launchMode="singleTop"
android:noHistory="true"/>
Good luck.
Stefan, you ever find an answer to this? I put together a testcase for this and am seeing the same (perplexing) behavior...I'll paste the code below in case anyone comes along and sees something obvious:
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example" >
<uses-sdk android:minSdkVersion="3"/>
<application android:icon="#drawable/icon" android:label="testSingleTask">
<activity android:name=".ActivityA"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ActivityB"/>
</application>
</manifest>
ActivityA.java:
public class ActivityA extends Activity implements View.OnClickListener
{
#Override
public void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
setContentView( R.layout.main );
View button = findViewById( R.id.tacos );
button.setOnClickListener( this );
}
public void onClick( View view )
{
//Intent i = new Intent( this, ActivityB.class );
Intent i = new Intent();
i.setComponent( new ComponentName( this, ActivityB.class ) );
startActivity( i );
}
}
ActivityB.java:
public class ActivityB extends Activity
{
#Override
public void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
setContentView( R.layout.layout_b );
}
}
I tried changing minSdkVersion to no avail. This just seems to be a bug, at least according to the documentation, which states the following:
As noted above, there's never more than one instance of a "singleTask" or "singleInstance" activity, so that instance is expected to handle all new intents. A "singleInstance" activity is always at the top of the stack (since it is the only activity in the task), so it is always in position to handle the intent. However, a "singleTask" activity may or may not have other activities above it in the stack. If it does, it is not in position to handle the intent, and the intent is dropped. (Even though the intent is dropped, its arrival would have caused the task to come to the foreground, where it would remain.)
I think this is the behaviour you want:
singleTask resets the stack on home press for some retarded reason that I don't understand.
The solution is instead to not use singleTask and use standard or singleTop for launcher activity instead (I've only tried with singleTop to date though).
Because apps have an affinity for each other, launching an activity like this:
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
if(launchIntent!=null) {
launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}
will cause your activty stack to reappear as it was, without it starting a new activity upon the old one (which was my main problem before). The flags are the important ones:
FLAG_ACTIVITY_NEW_TASK Added in API level 1
If set, this activity will become the start of a new task on this
history stack. A task (from the activity that started it to the next
task activity) defines an atomic group of activities that the user can
move to. Tasks can be moved to the foreground and background; all of
the activities inside of a particular task always remain in the same
order. See Tasks and Back Stack for more information about tasks.
This flag is generally used by activities that want to present a
"launcher" style behavior: they give the user a list of separate
things that can be done, which otherwise run completely independently
of the activity launching them.
When using this flag, if a task is already running for the activity
you are now starting, then a new activity will not be started;
instead, the current task will simply be brought to the front of the
screen with the state it was last in. See FLAG_ACTIVITY_MULTIPLE_TASK
for a flag to disable this behavior.
This flag can not be used when the caller is requesting a result from
the activity being launched.
And:
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED Added in API level 1
If set, and this activity is either being started in a new task or
bringing to the top an existing task, then it will be launched as the
front door of the task. This will result in the application of any
affinities needed to have that task in the proper state (either moving
activities to or from it), or simply resetting that task to its
initial state if needed.
Without them the launched activity will just be pushed ontop of the old stack or some other undesirable behaviour (in this case of course)
I believe the problem with not receiving the latest Intent can be solved like this (out of my head):
#Override
public void onActivityReenter (int resultCode, Intent data) {
onNewIntent(data);
}
Try it out!
I've found this issue happens only if the launcher activity's launch mode is set to singleTask or singleInstance.
So, I've created a new launcher activity whose launch mode is standard or singleTop. And made this launcher activity to call my old main activity whose launch mode is single task.
LauncherActivity (standard/no history) -> MainActivity (singleTask).
Set splash screen to launcher activity. And killed launcher activity right after I call the main activity.
public LauncherActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, HomeActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
startActivity(intent);
finish();
}
}
<activity
android:name=".LauncherActivity"
android:noHistory="true"
android:theme="#style/Theme.LauncherScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Launcher screen theme should be set for the case that app is restarting after the process is killed. -->
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:theme="#style/Theme.LauncherScreen"/>
Pros: Can keep the MainActivity's launch mode as singleTask to make sure there always is no more than one MainActivity.
If both A and B belong to the same Application, try removing
android:launchMode="singleTask"
from your Activities and test because I think the default behavior is what you described as expected.
Whenever you press the home button to go back to your home screen the activity stack kills some of the previously launched and running apps.
To verify this fact try to launch an app from the notification panel after going from A to B in your app and come back using the back button ..........you will find your app in the same state as you left it.
When using launch mode as singleTop make sure to call finish() (on current activity say A) when starting the next activity (using startActivity(Intent) method say B). This way the current activity gets destroyed.
A -> B -> Pause the app and click on launcher Icon, Starts A
In oncreate method of A, you need to have a check,
if(!TaskRoot()) {
finish();
return;
}
This way when launching app we are checking for root task and previously root task is B but not A. So this check destroys the activity A and takes us to activity B which is currently top of the stack.
Hope it works for you!.
This is how I finally solved this weird behavior. In AndroidManifest, this is what I added:
Application & Root activity
android:launchMode="singleTop"
android:alwaysRetainTaskState="true"
android:taskAffinity="<name of package>"
Child Activity
android:parentActivityName=".<name of parent activity>"
android:taskAffinity="<name of package>"
Add below in android manifest activity, it will add new task to top of the view destroying earlier tasks.
android:launchMode="singleTop" as below
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:launchMode="singleTop"
android:theme="#style/AppTheme.NoActionBar">
</activity>
In child activity or in B activity
#Override
public void onBackPressed() {
Intent intent = new Intent(getApplicationContext(), Parent.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
finish();
}
<activity android:name=".MainActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
//Try to use launchMode="singleTop" in your main activity to maintain single instance of your application. Go to manifest and change.

Categories

Resources