I have a Navigation Drawer with 6 Fragments. One of them should contain a ListView. I got it to work and I can switch through all the Fragments via the Navigation Drawer. When I press on the one containing the ListView I can see it, just as it should be. However, when I go to the same Fragment (containing the ListView) while I'm in it already, the ListView isn't displayed anymore. This is probably due to the fact, that I set the ListView on the onActivityCreated() method in the Fragment. I'm curious now though, what happens when I press on the same Fragment again. Does the activity stay loaded and the Fragment needs to be inflated again and through this the OnActivityCreated() method isn't called again? And if so, what do I have to do, that the ListView gets displayed again?
My Fragment.class currently looks like this:
public class FahrplanFragment extends Fragment {
private ListView listView;
private Resources res;
private String[] plans;
private ArrayAdapter<String> adapter;
public FahrplanFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_fahrplan, container, false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
listView = (ListView) getActivity().findViewById(R.id.listview_fahrplan);
res = getResources();
plans = res.getStringArray(R.array.plans);
adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1,
android.R.id.text1, plans);
listView.setAdapter(adapter);
}
}
As in the documentation
Called when the fragment's activity has been created and this
fragment's view hierarchy instantiated. It can be used to do final
initialization once these pieces are in place, such as retrieving
views or restoring state. [..] This is called after
onCreateView(LayoutInflater, ViewGroup, Bundle) and before
onViewStateRestored(Bundle).
It is called every time. You can check by logging the event.
Can you post the code of the event handling when click on the navagition drawer button?
EDIT 1
Here I re-created you project to test it and it works. The fragment loads every time with the list view inside. Moreover, it displays the events when they happens and shows clearly that onActivityCreated is called every time the fragment is replaced.
The best suggestion I can give here is to log your code using Log.d() or System.out.println("...") in various steps of the code, or even better enter in debug mode by setting breakpoints and inspect the code while it's running to understand when it enter in some methods and when not.
Related
I have implemented some fragments which are hosted by a single Activity (they're in the same activity). In one of these fragments (Let's say fragment A), I have a vertical recyclerview.
The problem is arises when I want to navigate from Fragment A to other fragments (Again, in the same activity). Suppose that I'm clicking the middle member of the list, then I navigate to the next fragment. When I want to back into the first fragment (Fragment A), it inflates the list from first. Seeming that it looses its position, however, using onSaveInstanceState() and also onCreateView(), I try to store and restore the selected item's position every time I quit or enter the fragment.
I think this issue may be originated from fragments' lifecycle which all are in one single activity. As this answer, mentioning :
In a Fragment, all of their lifecycle callbacks are directly tied to their parent Activity. So onSaveInstanceState gets called on the Fragment when its parent Activity has onSaveInstanceState called.
and that's why I emphasize on the "single" activity. How can we handle such situation?
I have debugged my program and onSaveInstanceState and onCreateView were not even called when they were supposed to. Below is my attempted code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState!=null){
selectedItem = savedInstanceState.getInt(BUNDLE_KEY_SELECTED_ITEM);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (savedInstanceState != null){
selectedItem = savedInstanceState.getInt(BUNDLE_KEY_SELECTED_ITEM);
}
myRecyclerView = (RecyclerView) v.findViewById(R.id.rv_mine);
// mData is a an ArrayList
myAdapter = new MyAdapter(mData, getContext(), this.selectedItem);
myLayoutManager = new LinearLayoutManager(getActivity());
myRecyclerView.setLayoutManager(myLayoutManager);
myRecyclerView.setAdapter(myAdapter);
return v;
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(BUNDLE_KEY_SELECTED_ITEM,selectedItem);
super.onSaveInstanceState(outState);
}
Well you can use .add() to add fragments to your activity than .replace(), Which preserves the state of the fragment within the activity, and if using replace function which will cause the onCreateView callback to trigger again.
I have 5 fragments in an activity. And they are being showed using tabs/viewpager. Suppose they are nameD as ONE, TWO, THREE, FOUR and FIVE. I am displaying data in all fragments as ListView. I am inflating data in ListView from database cursor. The data is updated normally when i perform an add or delete in the same fragment. The problem occurs when i send/move a data from one fragment to another.
EXAMPLE OF PROBLEM:
I send/move data from fragment ONE to fragment TWO. Then I tap on fragment TWO to view data. It is not there. The data is shown when I tap on fragment FOUR or fragment FIVE and then come back to fragment ONE. Or if the app is restarted or any other Activity comes in front and goes back.
The data is not shown by clicking the adjacent tabs or swapping to adjacent tabs. And then coming back to the tab from which data was moved
I am sure someone of you must have an idea whats happening here and tell me how to solve the issue.
onCreateView
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.frag_dramas, container, false);
setHasOptionsMenu(true);
list = (ListView) view.findViewById(R.id.mylist);
return view;
}
onResume
#Override
public void onResume() {
super.onResume();
getListView();
}
onViewCreated
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
add();
}
});
}
There are other methods in the fragment too but they are not relevant to the issue. I Think these three methods are relevant. And i have no onCreate() method in the fragment..
If Fragment is already in the memory you should use BroadcastReceiver to notify other fragments whenever any data is added/removed/updated.
You can try EventBus as well.
https://github.com/greenrobot/EventBus
I have an activity with a ViewPager and 3 fragments called A,B,C. A has a recyclerView populated by cardView and each card implements and OnClickListener which leads to a new Activity D. I want to be able to save the recyclerView scroll position when switching in tabs and when coming back from the opened activity D.
what i did so far in Fragment A (the one containing the RecyclerView looks like this :
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
recyclerView = (RecyclerView)inflater.inflate(R.layout.event_fragment_layout, container,false);
linearLayoutManager = new LinearLayoutManager((getActivity()));
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setHasFixedSize(true);
return recyclerView;
}
public void onStart(){
//This is where my adapter is created and attached to the recyclerView
recyclerView.setAdapter(myadapter)
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(RECYCLER_STATE,linearLayoutManager.onSaveInstanceState());
String saved = linearLayoutManager.onSaveInstanceState().toString();
Toast.makeText(getContext(),"Saving Instance State",Toast.LENGTH_SHORT).show();
Log.d("SAVE_CHECK ", saved);
}
#Override
public void onViewStateRestored(#Nullable Bundle savedInstanceState) {
if(savedInstanceState!=null) {
Parcelable savedRecyclerViewState = savedInstanceState.getParcelable(RECYCLER_STATE);
if(savedRecyclerViewState!=null) {
linearLayoutManager.onRestoreInstanceState(savedRecyclerViewState);
Toast.makeText(getContext(), "Restoring Instance State", Toast.LENGTH_SHORT).show();
}
else{
Toast.makeText(getContext(), "SAVED STATE IS NULL", Toast.LENGTH_SHORT).show();
}
}
super.onViewStateRestored(savedInstanceState);
}
I'm displaying both the Toast, so the block is executing (and that means the savedRecyclerViewState is not null) but the recyclerView always starts from the first Card no matter what. Also when i open a new Activity by clicking on a card and then come back to Fragment A i get the Toast notificaton that the state is being saved but not the one in onViewRestored.
What is basically happening from my understanding is that my linearLayoutManager's state gets saved but then i'm somehow failing to pass the info to restore the scrolling position. What am i doing wrong ?
SOLVED: solved it by myself but this might help some one it the future.
TL;DR -> just declare the adapter in the onCreateView and then call
yourRecyclerView.setAdapter(yourAdapter) both in OnCreateView and OnResume(),rest of the code remains the same
What happened then ? Simply enough i was correctly saving the linearLayoutManager state in onSavedInstanceState() and then correctly restoring it in OnCreate but i was unable to notice any difference it since the saved state state was being "overwritten" by setting my adapter in the OnStart.
In steps :
1)fragment activity calls onSavedInstanceState and saves the LinearLayoutManager of my RecyclerViewin a Parcelable.
2)the app goes back to the fragment and calls onViewStateRestored()/OnCreateView(),there we read from the parcelable and restore our recyclerViewState
3)Now the problem OnStart() gets called and of course this is where my adapter is declared and then set to the recyclerView. Now the old recyclerView(the one with the saved state) is gone and is replaced by a fresh new recyclerView with no record of the previous one.
So just by simply setting the adapter in the OnResume() method you'll solve the problem.
I guess you could also solve it by leaving the code in OnStart(), store the old recyclerView state and then passing it to the new recyclerView at the end of OnStart() but it's not a really clean solution
I have written android app now for a long time but now I'm facing a problem that I have never thought about. It is about the android lifecycle of Activitys and Fragments in in relation to configuration changes. For this I have create a small application with this necessary code:
public class MainActivity extends FragmentActivity {
private final String TAG = "TestFragment";
private TestFragment fragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
fragment = (TestFragment) fm.findFragmentByTag(TAG);
if (fragment == null) {
fragment = new TestFragment();
fm.beginTransaction().add(R.id.fragment_container, fragment, TAG).commit();
}
}
}
And here is my code for the TestFragment. Note that I'm calling setRetainInstance(true); in the onCreate method so the fragment is not recrated after a configuration change.
public class TestFragment extends Fragment implements View.OnClickListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater li, ViewGroup parent, Bundle bundle) {
View rootView = li.inflate(R.layout.fragment_test, parent, false);
Button button = (Button) rootView.findViewById(R.id.toggleButton);
button.setOnClickListener(this);
return rootView;
}
#Override
public void onClick(View v) {
Button button = (Button) v;
String enable = getString(R.string.enable);
if(button.getText().toString().equals(enable)) {
button.setText(getString(R.string.disable));
} else {
button.setText(enable);
}
}
}
And here is the layout that my fragment is using:
<LinearLayout
...>
<EditText
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="#+id/toggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/enable"/>
</LinearLayout>
My problem is that if I rotate the device the text of the Button change back to the default value. Of course the View of the Fragment is new created and inflated but the saved instance for the Views should be restored. I also have an EditText in my layout and there the text and other properties remains after the rotation. So why is the Button not restore from the Bundle by default? I have read on the developer site:
By default, the system uses the Bundle instance state to save information about each View object in your activity layout (such as the text value entered into an EditText object). So, if your activity instance is destroyed and recreated, the state of the layout is restored to its previous state with no code required by you.
I've also read a lot of answers the last days but I do not know how actual they are anymore. Please do not leave a comment or an answer with android:configChanges=... this is very bad practice. I hope someone can bring light into my lack of understanding.
You should save state of your fragment in the onSaveInstanceState(Bundle outState) and restore it in the onViewCreated(View view, Bundle savedState) method. This way you will end up with the UI just as it was before configuration change.
TextView subclasses don't save their text by default. You need to enable freezesText="true" in the layout, or setFreezesText(true) at runtime for it to save its state.
As per documentation, views should maintain their state without using setRetainInstance(true). Try to remove it from your onCreate, this should force the fragment to be recreated on screen rotation, hence all of it's views should be saved before rotation and restored after.
This stack overflow should answer your question:
setRetainInstance not retaining the instance
setRetainInstance does tell the fragment to save all of its data, and for ui elements where the user has manipulated the state (EditText, ScrollView, ListView, etc) the state gets restored. That being said, normal read-only UI components get reinflated from scratch in onCreateView and have to be set again - my guess would be that their properties are not considered "data" that needs to be retained and restored - Google probably does this for performance reasons. So things like a normal Button, ImageView, or TextView need their contents set manually when they are reinflated if it differs from the initial state in the XML. (TextView's android:freezesText basically puts the TextView in a mode that uses an implementation to save it's state and restore it).
PS: According to this stack overflow Save and restore a ButtonText when screen orientation is switched, you may be able to set android:freezesText on the button to have it keep the text you set - I haven't tried it, but it makes sense.
Edit after Op feedback
Although this fails to answer the question, after consultation with the OP, I've decided to leave it here as a reference point of information on the topic for people who land here. Hope you find it helpful.
Try putting your setRetainInstance in onCreateView. See here
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater li, ViewGroup parent, Bundle bundle) {
setRetainInstance(true);
View rootView = li.inflate(R.layout.fragment_test, parent, false);
Button button = (Button) rootView.findViewById(R.id.toggleButton);
button.setOnClickListener(this);
return rootView;
}
Called when the fragment's activity has been created and this
fragment's view hierarchy instantiated. It can be used to do final
initialization once these pieces are in place, such as retrieving
views or restoring state. It is also useful for fragments that use
setRetainInstance(boolean) to retain their instance, as this
callback tells the fragment when it is fully associated with the new
activity instance. This is called after onCreateView(LayoutInflater,
ViewGroup, Bundle) and before onViewStateRestored(Bundle).
developer.android.com/reference/android/app/Fragment.html#onActivityCreated
Control whether a fragment instance is retained across Activity
re-creation (such as from a configuration change). This can only be
used with fragments not in the back stack. If set, the fragment
lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because
the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being
re-created.
onAttach(Activity) and onActivityCreated(Bundle) will
still be called.
developer.android.com/reference/android/app/Fragment.html#setRetainInstance
And taken from here:
onCreate : It is called on initial creation of the fragment. You do
your non graphical initializations here. It finishes even before the
layout is inflated and the fragment is visible.
onCreateView : It is called to inflate the layout of the fragment i.e
graphical initialization usually takes place here. It is always called
sometimes after the onCreate method.
onActivityCreated : If your view is static, then moving any code to
the onActivityCreated method is not necessary. But when you - for
instance, fill some lists from the adapter, then you should do it in
the onActivityCreated method as well as restoring the view state when
setRetainInstance used to do so. Also accessing the view hierarchy of
the parent activity must be done in the onActivityCreated, not sooner.
Let me know if this helps.
I use a FragmentPagerAdapter to switch from fragments. I need some functions to be called when a fragmentswitch is made and had some troubles with OnPause and OnResume, so as suggested by THIS question I have implemented an interface OnPageSelectListener :
public interface OnPageSelectListener {
void onPageSelected();
void onPageNotVisible();
}
It calls the function OnPageSelected whenever this page comes to the foreground. Works nice, except that I want to call a function on my adapter. I thought that would work, except that my adapter returns NULL all the times (even though it is initialized and data is loaded in my listview as prefered).
public class AfterCheckFragment extends Fragment implements OnPageSelectListener{
private ListView listView;
private List<Check> checkList;
private CheckListAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_check, container, false);
System.out.println("VIEW create called");
//(.. some other stuff, not relevant for question..)
//initializing the adapter
listView = (ListView) view.findViewById(R.id.listView);
adapter = new CheckListAdapter(checkList,getActivity(),trainPosition);
listView.setAdapter(adapter);
adapter.handleButtonVisibility();
return view;
}
#Override
public void onPageSelected() {
if(this.adapter != null) {
System.out.println("adapter not null");
this.adapter.checkForActive();
}else{
System.out.println("Adapter is NULL");
}
}
#Override
public void onPageNotVisible() { //page is moved to backgroung
System.out.println("AFTER not active any more ");
}
}
Now is my question: Why does adapter (or any other object in the fragment) return null when I return to my fragment? When the fragmentPager is initialized the onActivityCreate function of the fragment is called one time, but after that not any more, and the adapter return null....
you have to call the onPageSelected() after initialization of the adapter and setAdapter() otherwise adapter will return null always
Here is why I think your CheckListAdapter (i'll call it listAdapter) is null:
You give the pagerAdapter to the ViewPager
The ViewPager asks the pagerAdapter for a new Fragment
The ViewPager tells the FragmentManager to use it
onPageSelected gets called
You try and use listAdapter. It hasn't been initialized yet at this point. (NPE)
The FragmentManager drags the Fragment through all its stages.
onCreateView gets called. Your listAdapter is created.
Don't try and use internal data of a fragment outside of it. It is meant to work as a standalone unit, it won't be very good if you use it differently. Since the fragment is initialized at a later stage, you can't use it like you intend.
You can try and do what you want to do in the fragment, rather than the pagerAdapter, or write a method in the hosting Activity and call it from the fragment when ready, or even launch an event.
ViewPager will create and destroy fragments as the user changes pages (see ViewPager.setOffscreenPageLimit()). So onActivityCreated() is only called on the fragment when it is being restored or set up for the first time. Hence, fragments can be created without ever having onActivityCreated() called.
Instead of onActivityCreated(), I would recommend overriding onViewCreated() and setting up your adapter there. No fragment can be displayed without having a view created, so this is a good place to do that kind of stuff.
If you have your OnPageSelectListener logic working, that's good. I found the best way to know when your fragment is actually in front of the user is by overriding setPrimaryItem() in the FragmentPagerAdapter. Getting the page out of view event is a little trickier, since you have to keep a reference to the fragment from the previous setPrimaryItem() call.
This is because Viewpager calls OnpageSelected way before Fragments in oncreateView()/onActivityCreated() is called .
The best way for you is to inflate your views in the constructor of the Fragment and set the Adapters.
Or
Use a member variable to store whether the Fragment is active or not. And use the variable in oncreateview() to call function on your adapter.
Why don't you use a viewpager.addOnPageChangeListener, in you pager , after setting its adapter and the setOffscreenPageLimit() instead of implements it on your fragment?
Heres a sample code:
viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
if(position == 1){ // if you want the second page, for example
//Your code here
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Make it in your Activity, where you setup your ViewPager, of course.
for me i had to call this on my viewpager:
myViewPager.setSaveFromParentEnabled(false);