I have an activity MainActivity there are three fragments associated with this activity.
Now one of my fragment Timeline has a listview. Which I populate from a Database in the backend. I use an AsyncTask to fetch values from the DB and process them to the List. I trigger this AsyncTask in the onCreate of the Fragment Timeline.
Now from Timeline on click of any list item I navigate to a different Activity called as DetailActivity
The problem is whenever I press back from the DetailActivity the onCreate of my MainActivity is called and my list refreshes again - the whole DB operation is called again and my list does not retain its state.
I am calculating the visible items of my List before I navigate away from the Fragment but I am forced to use static values for these variables so that I retain the position. How to avoid this?
Below are the snippets of my onPause and onResume as laid down in the fragment Timeline
static int index;
static int top;
#Override
public void onPause(){
System.out.println("onPause");
index = lv.getFirstVisiblePosition();
View v = lv.getChildAt(0);
top = (v == null) ? 0 : v.getTop();
super.onPause();
uiHelper.onPause();
}
#Override
public void onResume(){
super.onResume();
//dbHelper.open();
System.out.println("onResumr");
lv.setSelectionFromTop(index, top);
ActionBar actionBar = getActivity().getActionBar();
actionBar.setTitle("Timeline");
uiHelper.onResume();
AppEventsLogger.activateApp(getActivity());
updateUI();
}
This also forces my AsyncTask to run again and again, which is an overhead.
Edit:
The root of this problem - After struggling for so many days I borrowed a friends phone to test and all was sorted on this new phone. I found out that I had turned on the Do not keep Activities option in my Developer Settings. The Dumb me!!
This is, unfortunately, the default behavior of the Fragment class. A Fragment is destroyed whenever the containing Activity is paused, and recreated whenever the containing Activity is resumed. If you use an Activity instead of a Fragment for the list, you would not experience the same behavior. With an Activity:
AsyncTasks and/or web services would not be called again.
The list would show the previously scrolled position.
If you want the same behavior with a Fragment, you need to override the onSaveInstanceState() method. And while interesting, it is not a small amount of work.
EDIT:
Make sure the Do not keep Activities option is unselected in your phone's Developer Settings. This, though, does not change the essential behavior of the Fragment class that I have outlined above.
You can call setRetainInstance(true) on your fragment. The lifecycle will be slightly different though.
A nice view of a fragment's lifecycle is available here
http://corner.squareup.com/2014/10/advocating-against-android-fragments.html
Related
I'm a beginner in Android, so I apologize for the mistakes and I'd appreciate any constructive criticism.
I'm writing a basic application with a ListView of images, and when the user clicks on an item in the list, I want to display that image in a ViewPager, where the user can swipe back and forth to browse the whole list of images. Afterwards when the user presses the back button, I want to switch back to the ListView.
I manage the business logic in the MainActivity, which uses MainActivityFragment for the ListView and ImageHolderFragment for ViewPager.
The simplified code so far is as follows:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListItems = new ArrayList<>();
mListItemAdapter = new ListItemAdapter(this, R.layout.list_item, R.id.list_item_name, mListItems);
mListView = (ListView) findViewById(R.id.list_view_content);
mListView.setAdapter(mListItemAdapter);
mDeletedListItems = new ArrayList<>();
mViewPager = (ViewPager) getLayoutInflater().inflate(R.layout.image_display, null, true);
mImageAdapter = new ImageAdapter(getSupportFragmentManager(), mListItems);
mViewPager.setAdapter(mImageAdapter);
mViewPager.setOffscreenPageLimit(3);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mViewPager.setCurrentItem(position);
setContentView(mViewPager); // TODO: this is very wrong!
}
});
loadImages();
noContentText = (TextView) findViewById(R.id.no_content_text);
if (mListItems.isEmpty()) {
noContentText.setText(R.string.no_images);
} else {
mImageAdapter.notifyDataSetChanged();
}
}
Although this does work to some extent, meaning that it manages to display the ViewPager when an item in the list is clicked, there are two things about it ringing the alarm bells:
I've read that calling setContentView() for the second time in the same class is pretty much a sin. Nobody explained me why.
The back button doesn't work in this case. When it's pressed, the application is terminated instead of going back to the list view. I believe this is connected to the first point.
I would appreciate any help, explanations if my idea is completely wrong, and if my case is hopeless, I'd like to see a successful combination of ListView and ViewPager with transitions between each other.
Your activity already has R.layout.activity_main set as content view, which rightly displays the list view - that's what the responsibility of this activity is as you defined it. If we want to change what's shown on the screen, we should use a different instance of a building block (activity or fragment) to display the view pager images.
To say the least, imagine if you wanted to change the view to a third piece of functionality or UI, or a fourth... it would be a nightmare to maintain, extend and test as you're not separating functionality into manageable units. Fields that are needed in one view are mixed with those needed in another, your class file would grow larger and larger as each view brings its click listeners, callbacks, etc., you'd also have to override the back button so it does what you want - it's just not how the Android framework was designed to help you. And what if you wanted to re-use UI components in different contexts whilst tapping in to the framework's activity lifecycle callbacks? That's why fragments were introduced.
In your case, the list view could continue to run in your MainActivity and in your click listener, onItemClick you could start a new activity that will hold a viewPager:
Intent i = new Intent(MainActivity.this, MyLargePhotoActivityPager.class);
i.putExtra(KEY_POSITION, position);
// pass the data too
startActivityForResult(i, REQUEST_CODE);
Notice how you could pass the position to this activity as an int extra, in order for that second activity to nicely set the viewPager to the position that the user clicked on. I'll let you discover how to build the second activity and put the ViewPager there. You also get back button functionality assuming your launch modes are set accordingly, if needed. One thing to note is that when you do come back to the list View, you'd probably want to scroll to the position from the view pager, which is why you could supply that back as a result via a request code. The returned position can be supplied back to the list view.
Alternatively, you could use the same activity but have two fragments (see the link further above) and have an equivalent outcome. In fact, one of your fragments could store the list view, and the second fragment could be a fullscreen DialogFragment that stores a viewPager, like a photo gallery (some details here).
Hope this helps.
I've read that calling setContentView() for the second time in the
same class is pretty much a sin. Nobody explained me why.
Well, you kind of get an idea as to why.
When you use setContentView() to display another 'screen' you do no have a proper back stack.
You also keep references to Views (like mListView) that are not visible anymore and are therefore kind of 'useless' after you setContentView() for the second time.
Also keep in mind orientation changes or your app going to the background - you'll have to keep track of the state that your Activity was in which is way more complicated than it has to be if you have one Activity that does two different things.
You won't be arrested for doing things like you do right now, but it's just harder to debug and keep bug free.
I'd suggest using two different Activities for the two different things that you want to do, or use one Activity and two Fragments, swapping them back and forth.
If you insist on having it all in one Activity you need to override onBackPressed() (called when the user presses the back button) and restore the first state of your Activity (setContentView() again, pretty much starting all over).
What I Have
I have a ViewPager with 5 fragments. I want to animate some TextViews inside the fragments whenever they become visible to the user.
I can't use onResume() as the fragments to the left and right are already created. I can't use setUserVisibilityHint() as it is called before onCreateView() so the views are not ready yet.
So what should be the way to animate the views whenever a particular fragment becomes visible to the user?
I'm not sure, but if you say that setUserVisibilityHint calls before onCreateView, than check view on null here (make reference on view - field), and if it not null - animate it. Also animate it always in onCreateView.
(1) I can't use onResume() as the fragments to the left and right are already created.
(2) I can't use setUserVisibilityHint() as it is called before onCreateView() so the views are not ready yet.
So what should be the way to animate the views whenever a particular fragment becomes visible to the user?
You're right on (1) and (2). However, setUserVisibilityHint() gets called Once Again with a True value after the Fragment comes to Front on Display. But on First Run the Fragment to be shown gets its setUserVisibilityHint() called before onCreateView().
SOL: You should use the above said behaviour of setUserVisibilityHint() along with onResume() to animate the views whenever a particular fragment becomes visible to the user.
Scenario 1: On First Run: Displayed Fragment's setUserVisibilityHint(boolean isVisibleToUser) gets called with
True param value. But as the Fragment's State is not Resumed we postpone and let the onResume() handle animation.
Scenario 2: For Other Fragments that are already in Resume State, setUserVisibilityHint(boolean isVisibleToUser) will get called with
True param it they come on to Display. Here you check for the
Fragment Animated or not and Do animation.
CODE
a) Declare two Global Boolean Fields: isAnimated and isOnDisplay
a.1) Set isAnimated boolean to True;
b) Override setUserVisibilityHint(boolean isVisibleToUser):
Here you set isOnDisplay boolean to isVisibleToUser and check is the Fragment Not Already Animated and is in Resumed State and is Visible to User.
{ if(!isAnimated && isResumed() && isVisibleToUser) // DO Animation }
c) Override onResume()
Check if the Fragment Not Already Animated and is Visible to User.
{ if(!isAnimated && isVisibleToUser) // DO Animation }
I know this answer might be a bit late, but I hope it can help others in a similar situation.
You could use FragmentViewPager library (I am the author), which deals with the issue you are facing for you. Its features are:
allows its Fragment pages to get notified when they are actually
visible/invisible to the user
supports multiple levels of FragmentViewPagers (nesting)
provides methods to control its paging
A basic usage would be:
Attach FragmentViewPager programmatically or via XML to an Activity
or Fragment, as you would with native ViewPager
Set FragmentViewPager's adapter. Your adapter should inherit
com.sbrukhanda.fragmentviewpager.adapters.FragmentPagerAdapter or
com.sbrukhanda.fragmentviewpager.adapters.FragmentStatePagerAdapter
Override onResumeFragments() of the hosting Activity and call
FragmentViewPager.notifyPagerVisible():
private FragmentViewPager mFragmentsPager;
#Override
public void onResumeFragments() {
super.onResumeFragments();
mFragmentsPager.notifyPagerVisible();
...
}
or onResume() of the hosting Fragment and call
FragmentViewPager.notifyPagerVisible():
private FragmentViewPager mFragmentsPager;
#Override
public void onResume() {
super.onResume();
mFragmentsPager.notifyPagerVisible();
...
}
Override onPause() of the hosting Activity or Fragment and call
FragmentViewPager.notifyPagerInvisible():
private FragmentViewPager mFragmentsPager;
#Override
public void onPause() {
super.onPause();
mFragmentsPager.notifyPagerInvisible();
...
}
Implement FragmentVisibilityListener on all Fragment pages that you
wish to receive callbacks for their visibility state
You are ready to go!
If you wish to see a more complete sample code, then check project's sample project.
If you want to do it in individual fragments, then you can use isVisible()
for each fragment in your fragment transition and create a listener. Whenever a fragment will become visible , listener will be invoked and each fragment will implement that listener and do your intended task in the overridden method.
Here is my problem area:
I have a Fragment A. Once it is attached, in its onCreateView, I load a webservice to fetch the data from the server and after that I set that data on the list view using a Base Adapter. Now on the Item Clicks of the list view I replace the Fragment A with Fragment B using replace Methods of the Fragment Transactions and addtoBackstack("FragmentA").
FragmentManager fm =getActivity().getFragmentManager();
fm.beginTransaction().replace(R.id.content_frame, Fragment B).commit();
Now here when I press back button on Fragment B, it takes me to Fragment A but the webservice again starts loading.
My Problem: I just want that when it returns to Fragment A, it should show its previous state and should not call the webservices again.
Thanks
OnCreateView for a fragment runs on the creation of the view every time it needs to be drawn. By going back you are causing the view to be recreated and hence the webservices are loading again.
I believe that if you only want the web services to load once then you could move the code to the "onCreate" method instead, but its probably a better idea to move this code to "onResume" instead and include some logic that checks whether you need to load your webservices again or not.
This way everytime the fragment is paused and then loaded again you could ensure that the fragment still has everything it needs.
(source: xamarin.com)
EDIT:
So for example you could have
#Override
public void onResume() {
super.onResume(); // Always call the superclass method first
if (data == null) { //Or list is empty?
getWebData()
}
}
I am really confused with the internal state of a Fragment.
I have an Activity holding only one Fragment at once and replaces it, if another Fragment should get shown. From the docs onSaveInstanceState is called ONLY if the Activitys onSaveInstanceState is getting called (which isn't called in my case).
If I stop my Fragment, I'll store its state myself inside a Singleton (yeah, I know I hate Singletons, too, but wasn't my idea to do so).
So I have to recreate the whole ViewHirarchy, create new Views (by using the keyword new), restore its state and return them in onCreateView.
I also have a Checkbox inside this View from which I explicitly do NOT want to store its state.
However the FragmentManager wants to be "intelligent" and calls onViewStateRestored with a Bundle I never created myself, and "restores" the state of the old CheckBox and applies it to my NEW CheckBox. This throws up so many questions:
Can I control the bundle from onViewStateRestored?
How does the FragmentManager take the state of a (probably garbage-collected) CheckBox and applies it to the new one?
Why does it only save the state of the Checkbox (Not of TextViews??)
So to sum it up: How does onViewStateRestored work?
Note I'm using Fragmentv4, so no API > 17 required for onViewStateRestored
Well, sometimes fragments can get a little confusing, but after a while you will get used to them, and learn that they are your friends after all.
If on the onCreate() method of your fragment, you do: setRetainInstance(true); The visible state of your views will be kept, otherwise it won't.
Suppose a fragment called "f" of class F, its lifecycle would go like this:
- When instantiating/attaching/showing it, those are the f's methods that are called, in this order:
F.newInstance();
F();
F.onCreate();
F.onCreateView();
F.onViewStateRestored;
F.onResume();
At this point, your fragment will be visible on the screen.
Assume, that the device is rotated, therefore, the fragment information must be preserved, this is the flow of events triggered by the rotation:
F.onSaveInstanceState(); //save your info, before the fragment is destroyed, HERE YOU CAN CONTROL THE SAVED BUNDLE, CHECK EXAMPLE BELLOW.
F.onDestroyView(); //destroy any extra allocations your have made
//here starts f's restore process
F.onCreateView(); //f's view will be recreated
F.onViewStateRestored(); //load your info and restore the state of f's view
F.onResume(); //this method is called when your fragment is restoring its focus, sometimes you will need to insert some code here.
//store the information using the correct types, according to your variables.
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable("foo", this.foo);
outState.putBoolean("bar", true);
}
#Override
public void onViewStateRestored(Bundle inState) {
super.onViewStateRestored(inState);
if(inState!=null) {
if (inState.getBoolean("bar", false)) {
this.foo = (ArrayList<HashMap<String, Double>>) inState.getSerializable("foo");
}
}
}
I'm trying to create a ViewPager with six fragments but only 2nd fragment to 5th fragment contain data that I want to show and the first fragment and the last fragment I want to be used to reload the data and set the position to the 2nd fragment again. The overall flow is like this :
1st (reload and go back to 2nd) <- 2nd fragment <-> 5th fragment -> 6th fragment (same with 1st)
what I've tried is I create a callback from the 1st fragment and 6th fragment like this
public static class callbackFragmentLoading implements callbackFragmentLoad {
#Override
public void onLoading() {
mPager.setAdapter(mAdapter);
mPager.setCurrentItem(2,false);
}
}
and I passed the callback to the fragment constructor so I can called the onLoading function in the onActivityCreated. But I everytime I do it the application will be force closed and the logcat shows
recursive entry to executependingtransactions
is there any way to do this? or my method for doing it is wrong?
Thank You
is there any way to do this? or my method for doing it is wrong?
Messing with callbacks between Fragments of a ViewPager isn't probably such a good idea. Instead I would do it like this:
Don't load any data(like with a Loader) in the Fragments from the ViewPager, instead let the FragmentActivity do it(and the Fragments will get it through methods from the Activity).
Your two loading fragments(position 0 and 5) will call in their onResume method a reload action on the parent Activity(like a Loader restart)
At this moment the Activity will load/reload the data and when that finishes it will set the ViewPager to the correct items(either 1 or 4)
in the onResume method of the data fragments you'll refresh the fragment's data(here you may need to use some sort of signaling system because you'll need to duplicate the refresh code in the onCreateView(some fragments may have their view destroyed if they are far apart from the current visible position)).
As I don't know many things about the inner data fragment I've written a basic skeleton sample(without the data loading in the activity).