How to finish several SingleInstance Activities? - android

I have several activities with launchMode SingleInstance. On log out i want to finish all activities and open launchScreen.
val intent = Intent(context, LauncherActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
(context as AppCompatActivity).finishAffinity()
context.startActivity(intent)
However if i press back at Launcher activity i am forwarded to previously launched activities with singleInstance mode

UPDATE 11/1/2018:
I've tested multiple approach to deal with it such as event propagation, intent flags, counting activity instances, etc. There is some weird scenarios such as starting multiple singleInstance activities sequentially. In this case, middle activities doesn't start at all (onCreate method isn't called) and after pressing back button, they'll be started. Therefore no one of the prior approaches works! As the issue is a bit strange, I tried to solve it using a bit strange way.
We maintain the logout state in a singleton object called LogoutHandler. It cooperates with a class LogoutAwareActivity which is inherited by all activities except LoginActivity because it should not be affected by logout mechanism. When the logout occurs, a flag is set in the LogoutHandler until the last child of LogoutAwareActivity has finished then clears the flag.
Here is an implementation of that:
LogoutHandler:
import java.util.*
object LogoutHandler {
private var isLogout = false
private var timerWatchDog: TimerWatchDog? = null
fun isLogout() = isLogout
fun onActivityDestroyed() {
if (isLogout) {
timerWatchDog?.refresh(Runnable {
isLogout = false
timerWatchDog = null
})
}
}
fun logout() {
isLogout = true
timerWatchDog = TimerWatchDog(500)
}
private class TimerWatchDog(private val delay: Long) : Runnable {
private var timer: Timer? = null
private var runnable: Runnable? = null
fun refresh(runnable: Runnable) {
this.runnable = runnable
timer?.cancel()
val timerTask = object : TimerTask() {
override fun run() {
Thread(this#TimerWatchDog).start()
}
}
timer = Timer()
timer?.schedule(timerTask, delay)
}
override fun run() {
runnable?.run()
}
}
}
LogoutAwareActivity:
import android.support.v7.app.AppCompatActivity
abstract class LogoutAwareActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
if (LogoutHandler.isLogout()) {
finish()
}
}
override fun onDestroy() {
super.onDestroy()
LoginHandler.onActivityDestroyed()
}
}
A concrete Activity:
class ActivityA : LogoutAwareActivity() {
// ...
}
Another concrete Activity:
class ActivityB : LogoutAwareActivity() {
// ...
}
Your logout function:
fun logout() {
val intent = Intent(context, LoginActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
LogoutHandler.logout()
context.startActivity(intent)
}
Visual Result:
All of MainActivity, ActivityA, ActivityB and ActivityC are single instance.
Traversing between activities by pressing back button:
Going to LoginActivity then pressing back button:

Before launching splash screen add this line
ActivityCompat.finishAffinity(this)

I dont know what exactly you were trying to do but i got a feeling you could redesign your app differently to do it better.
Anyway to your question - I guess you can check onStart of the activities if the user has logged off, and if he did start a single instance launcher activity and close these activities with finish().

In my experience extending the Application class is the simpler and most effective way to store limited amount of data which needs to be shared among all the activities.
In your case you can create a class holding your login data and store its instance in your custom Application object, where it can be reached by all activities. They can check for login availability at start, subscribe for changes and get notified when they need to finish. The Application object itself can subscribe for changes and start the login activity if needed.

I have several activities with launchMode SingleInstance. On log out i want to finish all activities and open launchScreen.
Here is one way:
Have all the activities extend a custom BaseActivity.
You can use LocalBroadcastManager to send local broadcast (within your app), when the logout button is clicked.
Inside the base activity you can implement the listener. You can call finish() inside the listener.
So when user clicks the logout button, you send the local broadcast to all the activities open. Since all your activities extend the common BaseActivity, the listener gets called and the activities finish.
After sending the broadcast, you can open the intended LauncherActivity.
See How to use LocalBroadcastManager?
for more.
P.S: You can un-register the listener in onDestroy. Since activity is still present, onDestroy won't have been called. And if it has already been destroyed then you have one less activity to worry about.

Related

Android registerForActivityResult callback not called when Activity launched from another registerForActivityResult callback

I have these Activities, each one has its own navigation tree and a default (main) Fragment:
class MainActivity: Activity
class ActivityA: Activity
class ActivityB: Activity
In the fragment in MainActivity I have several activity launchers. When I call these launchers from an onClickListener (for example), things work as expected, I navigate to the destination Activity, I do what I have to do there, I finish the activity from its fragment:
activity.setResult(Activity.RESULT_OK)
activity.finish()
And finally I get the callback in the activity launcher from where the activity was launched.
But, when I launch an activity from another launcher, the callback will not be triggered.
Here it is a simple code to explain the problem:
The MainFragment of the MainActivity:
class MainFragment: Fragment {
val activityALauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
launchActivityB() // The callback in activityBLauncher will NOT be triggered after ActivityB is finished
}
val activityBLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
}
fun launchActivityA() {
val intent = Intent(activity, A::class.java)
activityALauncher.launch(intent)
}
fun launchActivityB() {
val intent = Intent(activity, B::class.java)
activityBLauncher.launch(intent)
}
// For example is called in onClickListener
fun someRandomFun() {
launchActivityB() // The callback in activityBLauncher will be triggered after ActivityB is finished
}
}
This is how ActivityA is finished from MainFragmentA:
class MainFragmentA: Fragment {
...
activity.setResult(Activity.RESULT_OK)
activity.finish()
...
}
This is how ActivityB is finished from MainFragmentB:
class MainFragmentB: Fragment {
...
activity.setResult(Activity.RESULT_OK)
activity.finish()
...
}
Any solution to this?
EDIT
For each registerForActivityResult we will get a callback in onActivityResult at the host activity MainActivity.
Usually, after onActivityResult in host activity, then the callback in registerForActivityResult will be triggered, this seems to be by design.
But in this particular case I never get the callback in registerForActivityResult but I still get in onActivityResult
this worked for me add this in your gradle
implementation 'androidx.activity:activity-ktx:1.2.0-alpha04'
implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha04'

Ignoring navigate() call: FragmentManager has already saved its state

I'm using navigation in MainActivity, then I start SecondActivity (for result). After finish of SecondActivity I would like to continue with navigation in MainActivity, but FragmentManager has saved his state already.
On Navigation.findNavController(view).navigate(R.id.action_next, bundle) I receive log message:
Ignoring navigate() call: FragmentManager has already saved its state
How I can continue in navigation?
You must always call super.onActivityResult() in your Activity's onActivityResult. That is what:
Unlocks Fragments so they can do fragment transactions (i.e., avoid the state is already saved errors)
Dispatches onActivityResult callbacks to Fragments that called startActivityForResult.
Finally, I fix the issue by simple calling super.onPostResume() right before navigating to restore state.
I've solved this problem this way:
#Override
public void onActivityResult() { //inside my fragment that started activity for result
model.navigateToResults = true; //set flag, that navigation should be performed
}
and then
#Override
public void onResume() { //inside fragment that started activity for result
super.onResume();
if(model.navigateToResults){
model.navigateToResults = false;
navController.navigate(R.id.action_startFragment_to_resultsFragment);
}
}
not sure, if this is not a terrible hack, but it worked for me. FramgentManager state is restored at this point (onResume) and no problems with navigation occur.
I believe above solutions should work. But my problem was different. There was a third party sdk which was launching its activity using context provided by me and it was delivering the result on a listener which I had to implement.
So there was no option for me to work with onActivityResult :(
I used below hack to solve the issue:
private var runnable: Runnable? = null // Runnable object to contain the navigation code
override fun onResume() {
super.onResume()
// run any task waiting for this fragment to be resumed
runnable?.run()
}
override fun responseListener(response: Response) { // Function in which you are getting response
if (!isResumed) {
// add navigation to runnable as fragment is not resumed
runnable = Runnable {
navController.navigate(R.id.destination_to_navigate)
}
} else {
// navigate normally as fragment is already resumed
navController.navigate(R.id.destination_to_navigate)
}
}
Let me know if there is any better solution for this. Currently I found this very simple and easy to implement :)
call super.onPostResume() before navigation....It's working

onCreate on new activity called multiple incremental times every time startActivity is invoked

I am working on an android app developed in kotlin that has two activities. Being that activity 1 is the main activity and when an specific layout is clicked activity 2, which is a dialog activity is started.
The problem I am having is that each time I click the specific layout on activity 1, the onCreate method on activity 2 is invoked (number of times it was invoked before) + 1 times. Meaning that the first time I click the layout are, the onCreate method is called one time. Then I close activity 2 with finish() and if I click the layout area again, the oncreate method of activity 2 is called two times. If I do that again, 3 times, and then 4, 5, etc.
Activity one goes like the below, where I add the onClick listener to layout area:
class ActivityOne : AppCompatActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_one)
Timber.plant(Timber.debugTree())
layoutAddNewProject.setOnClickListener(this)
}
override fun onClick(view: View?){
Timber.d("Click on view ${view.toString()}")
if (view?.id == layoutAddNewProject.id){
val intent = Intent(this, ActivityTwo::class.java)
startActivity(intent)
}
}
}
Then activity two goes like the below. Note that it has a button which when clicked closes the activity:
class ActivityTwo : AppCompatActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstance)
setContentView(R.layout.activity_two)
setFinishOnTouchOutside(false)
Timber.plant(Timber.DebugTree())
Timber.d("Starting activity two")
btnCancel.setOnClickListener(this)
}
override fun onClick(view: View?){
if (view?.id == btnCancel.id){
Timber.d("Cancel pressed")
finish()
}
}
}
The names of the activities, layout, etc are fictitious, but the code is inline with my real code.
So, the way I figure out onCreate on activity two is being called multiple times, is because I see the log message "Starting activity two" multiple times.
Note also, that when btnCancel is clicked on activity two, I also get the log message "Cancel pressed" multiple times (as many times as the number of times of the onCreate method being invoked).
Your help as always, is much appreciated.
Thank you.
When you call Timber#plant it adds new tree to the list. And then on every Timber#d it send message to all trees in that list. So to fix that, you need to call plant just once in your application class

Android stop activity in onCreate() before calling super.OnCreate()

I am creating an app having a navigation drawer activity with fragments. At every cold start of the app, I am executing some initialization code where I load the following things:
The user session(if the user is logged in or not)
Registering Retrofit services
Getting some data from the server to proceed with startup of the app.
This is the flow of my app when doing a cold start:
Starting MainActivity and verifying the user session.
If the session is valid, then we open the CoreActivity.
If not, then we open the LoginActivity.
When the app is brought to the foreground after some inactivity Android tries to restart the current Activity. This means my initialization code is bypassed and CoreActivity.onCreate() is executed.
All my activities(except MainActivity) are extending the following super activity:
public abstract class MasterActivity extends AppCompatActivity {
#Override
protected final void onCreate(Bundle savedInstanceState) {
this.supportRequestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
if (!CrmContext.getInstance().verifyContextSet(this)) {
return;
}
super.onCreate(savedInstanceState);
onCreateAfterContext(savedInstanceState);
}
In CrmContext:
public boolean verifyContextSet(final Context context) {
boolean isContextSet = applicationContext != null;
if (isContextSet) {
return true;
}
Intent intent = new Intent(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(intent);
return false;
}
In verifyContextSet() I am doing some checks to be sure that the app has been correctly loaded. If the user session is not properly loaded.
My problem:
If the app is brought to the front the CoreActivity.onCreate() is executed and verifyContextSet() returns false. In this case I want to cancel the creation of CoreActivity and open MainActivity again.
When I do my verifyContextSet() before super.onCreate(), then I get this exception:
android.util.SuperNotCalledException: Activity {nl.realworks.crm/nl.realworks.crm.view.CoreActivity} did not call through to super.onCreate()
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2287)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2391)
at android.app.ActivityThread.access$800(ActivityThread.java:151)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1309)
I tried to execute super.onCreate() first, but then the Fragment inside the activity is created first. This means that my Fragment is recreated before my verifyContextSet() is executed.
So, If I try to cancel()/finish() the onCreate() before super.onCreate() has been called, then I get the SuperNotCalledException. If I execute super.onCreate() first, then the Fragment is initialized which is not allowed when verifyContextSet() returns false.
I want to do the following:
Bringing the app to the foreground
Check if the app has been initialized
If not, then finish() the current activity and then restart the app to open MainActivity.
put your checking/validating code in an Application sub class
public class MyApp extends Application {
//in your oncreate create sessions etc.
now whether MainActivity restarts or not, you have already validated.
Note: Application class' onCreate() is the firs to run before any body.
What you need to do is to have your onCreate like this
super.onCreate();
if(<activity is not valid>) {
startAnotherActivity()
finish()
return;
}
This will make sure that no other activity lifecycle method is called except onDestroy i.e. onResume, onPause, onStop, onStart.
I think code should look like that
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Your code
}
super.onCreate(savedInstanceState) allways as first line.
You can use ViewModel and Observer. Basically your remaining onCreate code will only execute if Observer triggered.
onResume {
// THIS WILL TRIGGER THE OBSERVER
viewModel._needVerification.value = true
}
onCreate {
super.onCreate()
... CREATE VIEWMODEL
// THIS NEEDED TO HANDLE RECREATED ACTIVITY CAUSE BY SCREEN ORIENTATION ETC
viewModel._verificationFinished.value = false
viewModel.needVerification.observe(this, Observer{
if (it == true) {
verifyContextSet(final Context context) {
if (isContextSet) {
if (viewModel.verificationFinished.value != true) {
... DO REMAINING ONCREATE CODE
viewModel._verificationFinished.value = true
}
} else {
Start MainActivity
}
}
}
})
}
Instead of only returning from onCreate you need to finish the Activity first to stop other initialization callbacks to be triggered.
Just change this code
if (!CrmContext.getInstance().verifyContextSet(this)) {
return;
}
to this
if (!CrmContext.getInstance().verifyContextSet(this)) {
finish();
return;
}

Application Launch Count

I am working on an application, wherein after say 5 times the app is opened by a user, at 6th attempt the app should ask for feedback from user. I tried using Activity OnStart,OnResume, but its not working out since even after leaving and re-entering activity these methods are called. Also as per android functionality, I cannot quit app so that I can find it out from the first activity called. How do I find how many times the app was launched?
I hope this is not confusing.
Edit
Alternatively is there a way, wherein I can always resume my app from the first activity( or welcome page for eg.), once user presses home to quit the app.
This is actually quite simple. Using SharedPreference or the Database.
during OnCreate add 1 to the numberofTimes counter and commit.
OnCreate (Bundle bundle){
mPref = getPreferences();
int c = mPref.getInt("numRun",0);
c++;
mPref.edit().putInt("numRun",c).commit();
//do other stuff...
}
OnCreate is called regardless of you start the app or you resume the app, but isFinishing() returns true if and only iff the user (or you) called finish() on the app (and it was not being destroyed by the manager)
This way you only increment when you are doing fresh start.
the isFinishing() Method inside of a OnPause method to check to see if the activity is being finish() or just being paused.
#Override
protected void OnPause(){
if(!isFinishing()){
c = mPref.getInt("numRun",0);
c--;
mPref.edit().putInt("numRun",c).commit();
}
//Other pause stuff.
}
This covers all your scenarios:
1. user starts app/activity (+1)-> finishes app, exit with finish()
2. user starts app (+1) -> pause (-1) -> returns (+1)-> finish
3. user starts app (+1) -> pause (-1) -> android kills process (0) -> user returns to app (+1) -> user finish.
every scenario you only increment the "times run" counter once per "run" of the activity
Just:
declare:
private SharedPreferences prefs;
private SharedPreferences.Editor editor;
private int totalCount;
initialize in onCreate():
prefs = getPreferences(Context.MODE_PRIVATE);
editor = prefs.edit();
print or count wherever you want (any where in onCreate() or any specific click as you specified):
totalCount = prefs.getInt("counter", 0);
totalCount++;
editor.putInt("counter", totalCount);
editor.commit();
now print totalCount where you want to count e.g.:
System.out.println("Total Application counter Reach to :"+totalCount);
if you have a starting activity for app launch then you can implement it in following ways
1. Database:- through database you can save your application launch count and retrieve it on create of activity.
Static Variable:- static variable also retain values during application start and end
Application Preference:-you can store value in application preference and use it
problem with 2 and 3 approach is that if you switch off and on again your phone you will loose data. but if you still want to use 2 or 3 approach then 2 approach is very simple and
sample code for 3rd approach here
well you have to extends Application class and create a subclass from that
public class MyApp extends Application{
int visitCount;
onCreate(){
visitCount=0;
}
and you can mention it in your menifest file like
<application name="MyApp">
.....
</application>
and in onCreate of your activity you can get it by
MyApp myApp=(MyApp)getApplicationContext();
Edit1:
subclass your activity and override method
public class myActivity extends Activity{
#Override
onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
counterFlag=true;
}
}
it is get called when user press home button
and again override onResume() and check whether your counter flag is enabled or not
and create all your activity by subclassing your MyActivity
also if any other activity has exit point on click of back button then you can override
#Override
public void back_pressed(){
}
and do your task accordingly
I think this would be the best option in order to cover all scenarios:
private static boolean valueOfLaunchCountModified = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
if(!valueOfCountModified){
preferences = getPreferences(MODE_PRIVATE);
launchCount= preferences.getInt("launchCount", 0);
if(preferences.edit().putInt("launchCount", ++launchCount).commit()){
valueOfCountModified = true;
if(launchCount == 5){
//Do whatever you want
}
}
}
}
If we remember the definition of a static variable ("...They are associated with the class, rather than with any object. Every instance of the class shares a class variable...") we will discover that is perfect for us.
When onPause method or an orientation change is executed the value of "valueOfLaunchCountModified" doesn't change; however, if the app process is destroyed, the value of "valueOfLaunchCountModified" changes to false.
If you only want to count "true" invocations then extend Application and place counter logic into Application#onCreate. This could be a simple preference
I prefer to use onResume to track launch count since it’s getting called in every scenario (refer to Android Activity Lifecycle) when the activity is shown.
onResume could be called quite frequently depending on usage pattern, so instead of tracking launch count, it would be better to track launch session (as in only 1 launch count would be tracked per hour).
#Synchronized fun appSessionCount(sharedPref: SharedPreferences): Boolean {
val now = LocalDateTime.now(ZoneOffset.UTC)
val firstSeconds = sharedPref.getLong(KEY_FIRST_LAUNCH_DATE, 0)
if (firstSeconds == 0L) {
sharedPref.edit {
putLong(KEY_FIRST_LAUNCH_DATE, now.atZone(ZoneOffset.UTC).toEpochSecond())
}
}
val seconds = sharedPref.getLong(KEY_LAST_SESSION_DATE, 0)
val lastDate = if (seconds > 0) LocalDateTime.ofInstant(Instant.ofEpochSecond(seconds), ZoneOffset.UTC) else null
var count = sharedPref.getLong(KEY_SESSION_COUNT, 0)
// first time or 1 hour ago
if (lastDate == null || Duration.between(lastDate, now).toHours() >= 1) {
sharedPref.edit {
putLong(KEY_SESSION_COUNT, count + 1)
putLong(KEY_LAST_SESSION_DATE, now.atZone(ZoneOffset.UTC).toEpochSecond())
}
return true
}
return false
}
I run the code at onResume of my main activity.
class MainActivity : AppCompatActivity() {
lateinit var sharedPref: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
sharedPref = getSharedPreferences("LuaApp", Context.MODE_PRIVATE)
}
override fun onResume() {
super.onResume()
appSessionCount(sharedPref)
}
}
https://code.luasoftware.com/tutorials/android/android-track-app-launch-count/

Categories

Resources