How to check if the fragment is visible to user - android

How to check if a fragment is visible to the user in an Android app?
I have an app where the Home activity has several fragments. One of the fragments listens to events from another activity started from Home and hence executing a method every time I come back to Home from that activity. While what I want is to only trigger those events and have the method executed if the fragment is visible to the user in the foreground.
I have tried the following but nothing seemed to work:
isAdded() and isVisible() check - always return true as soon as the fragment the created, irrespective of fragment actually visible to the user or not.
getUserVisibleHint() - this again would return true even if I was still in the other activity. Additionally, it is deprecated as well, so even if it worked I would want to look for another solution.
I have come to a dead-end to find an actual working way to check if the fragment is actually visible to the user and not just added to memory.
Any help on this is appreciated!

I finally came across the solution while trying myself:
Simple check for isResumed() and it'd only return true if the fragment is in the foreground and resumed state.
Just to be safe, check for isVisible which also checks for isAdded internally and would reduce the condition, so a simple if-condition:
if (isVisible() && isResumed()) {
// perform your action here
}
This worked for me!

Related

Handle onViewCreated() of the previous fragment when the back button pressed

I have the ResultFragment. The onViewCreated() method has some logic regarding saving the result. If a user has earned an achievement, a button appears and he can go to the AchievementFragment. However, when the user presses the back button from there, he goes back to ResultFragment and triggers the saving again and that leads to a result duplicate.
How can I handle this behavior? I can check if the result is a duplicate, but this is going to hide a symptom and not solve the problem.
If you want to prevent the application from triggering the saving of the result, which leads to duplications when going back, make use of Boolean.
Below there is a simplified pseudo code of what you could use and adapt to your project:
ResultFragment:
Boolean resultObtained = false // place it somewhere that is not reset when going back
if (resultObtained == true) {
// do not save result
}
else {
// save result
}
The Boolean can be reset to false when you go back to the homepage. While is true, the ResultFragment should not duplicate the result if you nest the function inside the if statement using the Boolean.
For the Boolean, you could add it somewhere else and make it static, so it is easily accessible from all the fragments. Additionally, you can keep in the same fragment, but you would need to write more code to ensure that it is not reset to false again when going back. Alternatively, you may place it directly in the AchievementFragment and make it static so it stays true when you go back. However, it is up to you how you want to adapt it in your code.

How to show disclaimer once per run of app

This is harder than it sounds, which is why I'm asking for solutions.
Basically I only want the disclaimer Toast shown once per run of the app.
The app is in two parts, all are Activities.
It's shown when it starts in the first part, but you can hit a menu button
taking you to the second part of the app, which has another menu button
to take you back to the first.
The problem is that whatever initial settings you try to make in the first
part, when it starts up, are run again when returning from the second
part of the app, so it'll show again.
My last idea was that in the first part's onDestroy(), when the app exits, but is not the case in this situation, you set a boolean in settings,
to reset that the disclaimer can be shown, but apparently, onDestroy() is called on the first part before it goes to the second part.
Or, if you can get it to not show the first run, but behave properly
every time after that, that would be okay.
And there doesn't seem to be any method to be called when the app truly
is "killed", if there was that would be the way to do it, you could reset it there. Or if there was a method that was only called when the app first started..
Thanks!
You just need a boolean flag. Say we call it disclaimerShown. In onCreate() of Activity A, we check both the Intent Bundle and the savedInstanceState Bundle for this flag.
You can add the boolean to a Bundle when launching the Intent to start Activity A from Activity B.
If the user is in Activity B and presses the Back button to return to Activity A, you can override onBackPressed() in Activity B and include your flag there as well (though you'll have to catch this flag on onActivityResult() in Activity A).
If system initiated process death occurs in Activity A, the system will call onSaveInstanceState(Bundle bundle). So you add your flag to this bundle as well.
And if system initiated process death occurs in Activity B, you have nothing to worry.
And that handles all possible cases.
An elegant solution for this problem would be the ProcessLifecycleOwner.
This class provides callbacks to the lifecycle of your whole app (not individual activities) and you could use the Lifecycle.Event.ON_CREATE callback to show your toast once. Look at this stackoverflow question for a usage example of the ProcessLifecycleOwner.
It turns out that I already had an Activity that started before
my "Activity A", and I moved my disclaimer Toast there
and it works fine. You can't beat that simplicity lol.
Thanks for your answers!

Save the number of the current page of ViewPager

I have a ViewPager with a FragmentStatePagerAdapter and the starting page should be somewhere in the middle. But when the user goes to another Activity and then comes back, the page number should be saved, so that he returns to the same page. There is probably an easy way to get this done, but I can't find the solution.
Usually the current page number will be saved, so this is not the point. The problem is that I don't know where to put the code that sets the starting page at first, because when I put it in onCreate, the starting page will be shown even if I come back from a different Activity.
I also tried saving the number in a variable in the onPause method:
this.currentPageNumber = viewPager.getCurrentItem();
But next time when onStart() is called, the variable currentPageNumber is null, so it seems that variables are not saved after onDestroy().
I could use a static variable, but it feels wrong. Is there a better solution?
EDIT: Sorry, I didn't make clear enough, that I want the saved page only be opened, if I come back to this Activity after I launched it. Every time I start the Activity from the launcher, the starting page should be shown and not the saved page.
EDIT 2: The behaviour I want to have is exactly the same as the Google Calendar app has when you open the day or week perspective. If I open this app, the current day will be shown. If I swipe to another day, then open settings, then press back, this other day is still be shown. But if I restart the app, again today will be shown.
After you have initialised your viewpager, use this method :
viewPager.setCurrentItem(int position)
Or this :
viewPager.setCurrentItem(int position, boolean withAnimation)
You can save the last position by using SharedPreference in the onPageSelect() method where you can get the position of each page. You have to implement the OnPageChangeListner in order to have this method.
Edit
Your question wasn't clear :
Sorry, maybe I didn't express my problem well enough. I want the starting page to appear everytime I start my app. But if I enter the activity by the back-button (for example if I opened my settings activity and then go back) the last viewed page should be shown. The solution provided in the link will lead to the safed page everytime I open the app
I don't know why you want to change this, it's a normal behavior, read this.
But if you insist, you can always use setCurrentItem() method in the onResume of your Activity/Fragment, thus the first page will always be shown after your Activity/Fragment gets resumed.
Edit 2
That can still be done by setCurrentItem. In your adapter, try to detect the index of the page of the current day. Create a method that returns that field from outside the adapter. And then after you have initialised your ViewPager,
viewpager = (ViewPager) findViewById(R.id.viewpager);
viewpager.setAdapter(adapter);
setCurrentItem(adapter.getCurrentDayPosition()) // or something like that ...
The method in the adapter :
public int getCurrentDayPosition() {
return this.currentDayPosition // this a field of the adapter.
}
I have the same requirement to show the default page whenever the onCreate of the ViewPager hosting Activity is called and show the current page for all the other cases including the motivating case: when user navigate back from the Activity started from the current page (onCreate won't be called in this case). My solution is based on the time spend between onCreate and onResume.
If the time between onCreate and onResume is shorter then a threshold (I use 1s assuming onCreate could be finished within 1s which we really wanted for the good programming practice), then we believe this is the first time the onStart/onResume is called right after the onCreate, otherwise we believe it is about all the other cases including our motivating case.
Then we only need to set the current timestamp in onCreate and do the time comparison in onResume to make the decision to set the current item of ViewPager to default or current. Note that we need to save the current page number in onPause in SharedPreference.
I am not claiming this idea is perfect but it works fine for me so far. Let me know if you are interested in the implementation and I will post my code upon your request.
i think this solve your problem , because first time you launch your activity there is no instance saved (savedInstanceState) ...
read comments in the post ...Android :Save View Pager State ...

Is there anything I need to take into account when repeatedly calling the same activity?

I am developing an app where a single activity is instantiated multiple times by itself. I guess you could think of it like a book where each activity is a page. At the end of the page (activity) the user presses a button to go to a new page. I fire off an Intent for the same Activity, but I push different data into the Bundle so that a different page is loaded.
This works fine, and I like the fact that the user can back up to a previous point, but my question is whether this will eventually be a problem? What happens if this activity is instantiated 10 times, or 50, or 100? Will the device run out of memory, will GC come along and clean up old activities, or what?
If GC does clean them up, what happens when the user presses Back and the previous Activity is no longer on the stack?
Is it better to keep track of the user's path, finish() the activity, and override the Back button so that whether the user is moving forward or backwards, I only load a single Activity? Another approach I could take is to refresh all the data on the page so that it's still the same activity, but with new data. The Back button would not work as expected in this case.
Thoughts?
have you considered perhaps using the same Activity and just changing the content that it renders.
So in the book example. You would have a book Activity which would have a Page ViewGroup somewhere in its view hierarchy that renders the contents of the page. Then when a user goes to the next page or the previous page, the Page ViewGroup simply renders the contents of the desired Page. You could then use a data-structure to manage your stack of pages and the users current position.
Not sure what your app is trying to do, so I understand this might not work for your particular use. However, I would expect this to have a better run-time performance than instantiating entire Activities.
Old Activities that are no longer visible will get destroyed if their memory is needed. However, Android has a state persistence mechanism in place so that when an Activity is restarted (navigating back to page that was destroyed in your case) it can be reconstructed properly. This can be done through the shared preferences mechanism or the bundle object passed into Activity.onCreate. However, you will have to explicitly save off the state in Activity.onStop(...) and Activity.onSaveInstanceState(...), and then restore the state in Activity.onCreate(...) and Activity.onRestoreInstanceState
More on the lifecycle of Activies can be read here (Not sure what your level of understanding is)
http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle:
Now I'm not sure what happens at the extremes of this, whether you can create so many Activities that Android will no longer be able to bring back ones it killed. I would expect that there's some sort of protection mechanism in place to prevent that but I don't know what it is.
Found this article which might provide some more info, not sure if this is the info you were looking for though:
http://zerocredibility.wordpress.com/2009/08/24/why-android-swap-doesnt-make-sense/
Cheers & Happy Hunting!
I would rather override the back button behaviour and using for instance a ViewFlipper to do the animation job. That's pretty simple to do:
// You could do simpler by overriding onBackPressed() if you
// don't need 1.6 compatibility
//--
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
if (currentPage>0) {
return super.onKeyDown(keyCode, event);
} else {
currentPage--;
showPage(currentPage);
return true;
}
}
return super.onKeyDown(keyCode, event);
}

Does pressing Back always cause Activity to finish()?

I've heard that pressing the back button will essentially cause the current Activity to finish(). Is this always the case? Seems like it would be with the way it pops the Activity off the stack.
The one situation I'm not so sure about is when the root Activity in a Task has back pressed. I'm currently experiencing a very weird effect, described as follows:
On loading my application, the first Activity is for initialization, and once it finishes, it calls my main Activity (a TabActivity). This first init activity has android:noHistory="true" set in the Manifest so pressing Back from my main Activity won't go back to that. It goes to the Launcher. When I click on my App in the Launcher a second time, the initialization activity loads again, and loads the main Activity when done. Almost immediately after, it loads a second instance of my main Activity. But ONLY after the Application has already been run once, and was exited by pressing BACK from the main Activity. It does it every subsequent time until I force quit the app or load a new version from the IDE.
Based on this, I am suspecting some kind of Activity instance is lying around and being reused, since it only happens on the second+ time I run the application (and exit with BACK -- using HOME just returns to the last state of the app, no big deal). Anyone have any thoughts??
I've heard that pressing the back button will essentially cause the current Activity to finish(). Is this always the case?
No it is not. The most activities have this behaviour but not all. For example you could create a Dialog and set it setCancelable(false) and it won't close if you click BACK button.
Furthermore you could customize activity behaviour on BACK button pressed by overriding onBackPressed
Called when the activity has detected the user's press of the back key. The default implementation simply finishes the current activity, but you can override this to do whatever you want.
About your application behaviour..Did you verify if the activity launcher is finished after it loads your main activity? I mean if the onDestroy() method is called. Maybe after it runs the main activity it remains there and when you click back you just go back to the old Launcher...
hope this helps..
Read through the Activity and Task design guidelines on the Android developer site; they explain how the Home and Back buttons work. Obviously, if you override the default behavior (as mentioned by hara above), the back button will not finish the activity.
On your specific issue, check your logcat. You should be able to see there whether it is bringing an old process back to life or starting up a new one. If that is unclear, insert a couple of log statements into onCreate, onPause, onDestroyed, etc., so that you can see exactly what is happening with your process.
You can control BACK-BUTTON by writing the following code.
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_BACK ) {
//preventing default implementation previous to
//android.os.Build.VERSION_CODES.ECLAIR
return false;
}
return super.onKeyDown(keyCode, event);
}
Are you running your activities with any special flags, such as singleInstance or singleTop? Those could be causing the oddities you're seeing. The easiest way to track down what's causing your problem is to absolutely fill it with debugging messages. For example:
In your initialisation activity, add a log in the beginning of onCreate to get the name of the activity such as this.toString(). More on why you want this line later.
When it launches the main tabbed activity, get the name of the launching activity and a message saying it's launched the tabbed one.
Override the onPause(), onStop() and onDestroy() callbacks and add debugging lines with this.toString() and also a message telling you which callback it is.
What this will do is tell you whether you've got multiple instances of the initialisation activity lying around. To this by comparing the name of the activities calling your main activity with the ones that were just created and the ones that went through to onDestroy.
If you don't know how to debug, use Log.d(LOG_TAG, "Your message here");. And then define a constant LOG_TAG String somewhere. After that, show the LogCat perspective in Eclispe by going to Window, show perspective (or view, don't remember exactly), other, Android, LogCat. The purpose of having a LOG_TAG constant is that you can set up LogCat to filter to that String and only show you those messages. It will make it easier to see them among the mass of system log messages.
The short answer to the original question is 'no'. This is largely because, unfortunately, not every developer follows the guidelines referenced by previous answers.
Yet the guidleines themselves mention exceptions, when the Back key should not call finish(). the most prominent exception is the Web browser, which has its own "back stack" for each window, so it must have its own custom handling of the Back key.
If there are no fragments on the back stack and a developer has not overridden onBackPressed, the activity will finish when the back button is pressed.
Here is the source code for Android 4.4.2 Activity.onBackPressed():
public void onBackPressed() {
if (!mFragments.popBackStackImmediate()) {
finish();
}
}
just override onbackpressed().. on back press this method will get execute remove super and do what u want to do.

Categories

Resources