I've written up a dummy activity that switches between two fragments. When you go from FragmentA to FragmentB, FragmentA gets added to the back stack. However, when I return to FragmentA (by pressing back), a totally new FragmentA is created and the state it was in is lost. I get the feeling I'm after the same thing as this question, but I've included a complete code sample to help root out the issue:
public class FooActivity extends Activity {
#Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(android.R.id.content, new FragmentA());
transaction.commit();
}
public void nextFragment() {
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(android.R.id.content, new FragmentB());
transaction.addToBackStack(null);
transaction.commit();
}
public static class FragmentA extends Fragment {
#Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View main = inflater.inflate(R.layout.main, container, false);
main.findViewById(R.id.next_fragment_button).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((FooActivity) getActivity()).nextFragment();
}
});
return main;
}
#Override public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save some state!
}
}
public static class FragmentB extends Fragment {
#Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.b, container, false);
}
}
}
With some log messages added:
07-05 14:28:59.722 D/OMG ( 1260): FooActivity.onCreate
07-05 14:28:59.742 D/OMG ( 1260): FragmentA.onCreateView
07-05 14:28:59.742 D/OMG ( 1260): FooActivity.onResume
<Tap Button on FragmentA>
07-05 14:29:12.842 D/OMG ( 1260): FooActivity.nextFragment
07-05 14:29:12.852 D/OMG ( 1260): FragmentB.onCreateView
<Tap 'Back'>
07-05 14:29:16.792 D/OMG ( 1260): FragmentA.onCreateView
It's never calling FragmentA.onSaveInstanceState and it creates a new FragmentA when you hit back. However, if I'm on FragmentA and I lock the screen, FragmentA.onSaveInstanceState does get called. So weird...am I wrong in expecting a fragment added to the back stack to not need re-creation? Here's what the docs say:
Whereas, if you do call addToBackStack() when removing a fragment,
then the fragment is stopped and will be resumed if the user navigates
back.
If you return to a fragment from the back stack it does not re-create the fragment but re-uses the same instance and starts with onCreateView() in the fragment lifecycle, see Fragment lifecycle.
So if you want to store state you should use instance variables and not rely on onSaveInstanceState().
Comparing to Apple's UINavigationController and UIViewController, Google does not do well in Android software architecture. And Android's document about Fragment does not help much.
When you enter FragmentB from FragmentA, the existing FragmentA instance is not destroyed. When you press Back in FragmentB and return to FragmentA, we don't create a new FragmentA instance. The existing FragmentA instance's onCreateView() will be called.
The key thing is we should not inflate view again in FragmentA's onCreateView(), because we are using the existing FragmentA's instance. We need to save and reuse the rootView.
The following code works well. It does not only keep fragment state, but also reduces the RAM and CPU load (because we only inflate layout if necessary). I can't believe Google's sample code and document never mention it but always inflate layout.
Version 1(Don't use version 1. Use version 2)
public class FragmentA extends Fragment {
View _rootView;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (_rootView == null) {
// Inflate the layout for this fragment
_rootView = inflater.inflate(R.layout.fragment_a, container, false);
// Find and setup subviews
_listView = (ListView)_rootView.findViewById(R.id.listView);
...
} else {
// Do not inflate the layout again.
// The returned View of onCreateView will be added into the fragment.
// However it is not allowed to be added twice even if the parent is same.
// So we must remove _rootView from the existing parent view group
// (it will be added back).
((ViewGroup)_rootView.getParent()).removeView(_rootView);
}
return _rootView;
}
}
------Update on May 3 2005:-------
As the comments mentioned, sometimes _rootView.getParent() is null in onCreateView, which causes the crash. Version 2 removes _rootView in onDestroyView(), as dell116 suggested. Tested on Android 4.0.3, 4.4.4, 5.1.0.
Version 2
public class FragmentA extends Fragment {
View _rootView;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (_rootView == null) {
// Inflate the layout for this fragment
_rootView = inflater.inflate(R.layout.fragment_a, container, false);
// Find and setup subviews
_listView = (ListView)_rootView.findViewById(R.id.listView);
...
} else {
// Do not inflate the layout again.
// The returned View of onCreateView will be added into the fragment.
// However it is not allowed to be added twice even if the parent is same.
// So we must remove _rootView from the existing parent view group
// in onDestroyView() (it will be added back).
}
return _rootView;
}
#Override
public void onDestroyView() {
if (_rootView.getParent() != null) {
((ViewGroup)_rootView.getParent()).removeView(_rootView);
}
super.onDestroyView();
}
}
WARNING!!!
This is a HACK! Though I am using it in my app, you need to test and read comments carefully.
I guess there is an alternative way to achieve what you are looking for.
I don't say its a complete solution but it served the purpose in my case.
What I did is instead of replacing the fragment I just added target fragment.
So basically you will be going to use add() method instead replace().
What else I did.
I hide my current fragment and also add it to backstack.
Hence it overlaps new fragment over the current fragment without destroying its view.(check that its onDestroyView() method is not being called. Plus adding it to backstate gives me the advantage of resuming the fragment.
Here is the code :
Fragment fragment=new DestinationFragment();
FragmentManager fragmentManager = getFragmentManager();
android.app.FragmentTransaction ft=fragmentManager.beginTransaction();
ft.add(R.id.content_frame, fragment);
ft.hide(SourceFragment.this);
ft.addToBackStack(SourceFragment.class.getName());
ft.commit();
AFAIK System only calls onCreateView() if the view is destroyed or not created.
But here we have saved the view by not removing it from memory. So it will not create a new view.
And when you get back from Destination Fragment it will pop the last FragmentTransaction removing top fragment which will make the topmost(SourceFragment's) view to appear over the screen.
COMMENT: As I said it is not a complete solution as it doesn't remove the view of Source fragment and hence occupying more memory than usual. But still, serve the purpose. Also, we are using a totally different mechanism of hiding view instead of replacing it which is non traditional.
So it's not really for how you maintain the state, but for how you maintain the view.
I would suggest a very simple solution.
Take the View reference variable and set view in OnCreateView. Check if view already exists in this variable, then return same view.
private View fragmentView;
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (fragmentView != null) {
return fragmentView;
}
View view = inflater.inflate(R.layout.yourfragment, container, false);
fragmentView = view;
return view;
}
I came across this problem in a Fragment containing a map, which has too many setup details to save/reload.
My solution was to basically keep this Fragment active the whole time (similar to what #kaushal mentioned).
Say you have current Fragment A and wants to display Fragment B.
Summarizing the consequences:
replace() - remove Fragment A and replace it with Fragment B. Fragment A will be recreated once brought to the front again
add() - (create and) add a Fragment B and it overlap Fragment A, which is still active in the background
remove() - can be used to remove Fragment B and return to A. Fragment B will be recreated when called later on
Hence, if you want to keep both Fragments "saved", just toggle them using hide()/show().
Pros: easy and simple method to keep multiple Fragments running
Cons: you use a lot more memory to keep all of them running. May run into problems, e.g. displaying many large bitmaps
onSaveInstanceState() is only called if there is configuration change.
Since changing from one fragment to another there is no configuration change so no call to onSaveInstanceState() is there. What state is not being save? Can you specify?
If you enter some text in EditText it will be saved automatically. Any UI item without any ID is the item whose view state shall not be saved.
first: just use add method instead of replace method of FragmentTransaction class then you have to add secondFragment to stack by addToBackStack method
second :on back click you have to call popBackStackImmediate()
Fragment sourceFragment = new SourceFragment ();
final Fragment secondFragment = new SecondFragment();
final FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.add(R.id.child_fragment_container, secondFragment );
ft.hide(sourceFragment );
ft.addToBackStack(NewsShow.class.getName());
ft.commit();
((SecondFragment)secondFragment).backFragmentInstanceClick = new SecondFragment.backFragmentNewsResult()
{
#Override
public void backFragmentNewsResult()
{
getChildFragmentManager().popBackStackImmediate();
}
};
Kotlin and ViewBinding Solution
I am using replace() and backstack() method for FragmentTransaction. The problem is that the backstack() method calls the onCreateView of the Previous Fragment which causes in re-built of Fragment UI. Here is a solution for that:
private lateinit var binding: FragmentAdRelevantDetailsBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?
): View {
if (!this::binding.isInitialized)
binding = FragmentAdRelevantDetailsBinding.inflate(layoutInflater, container, false)
return binding.root
}
Here, since onSaveInstanceState in fragment does not call when you add fragment into backstack. The fragment lifecycle in backstack when restored start onCreateView and end onDestroyView while onSaveInstanceState is called between onDestroyView and onDestroy. My solution is create instance variable and init in onCreate. Sample code:
private boolean isDataLoading = true;
private ArrayList<String> listData;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
isDataLoading = false;
// init list at once when create fragment
listData = new ArrayList();
}
And check it in onActivityCreated:
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if(isDataLoading){
fetchData();
}else{
//get saved instance variable listData()
}
}
private void fetchData(){
// do fetch data into listData
}
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener()
{
#Override
public void onBackStackChanged()
{
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
{
//setToolbarTitle("Main Activity");
}
else
{
Log.e("fragment_replace11111", "replace");
}
}
});
YourActivity.java
#Override
public void onBackPressed()
{
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.Fragment_content);
if (fragment instanceof YourFragmentName)
{
fragmentReplace(new HomeFragment(),"Home Fragment");
txt_toolbar_title.setText("Your Fragment");
}
else{
super.onBackPressed();
}
}
public void fragmentReplace(Fragment fragment, String fragment_name)
{
try
{
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.Fragment_content, fragment, fragment_name);
fragmentTransaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);
fragmentTransaction.addToBackStack(fragment_name);
fragmentTransaction.commitAllowingStateLoss();
}
catch (Exception e)
{
e.printStackTrace();
}
}
My problem was similar but I overcame me without keeping the fragment alive. Suppose you have an activity that has 2 fragments - F1 and F2. F1 is started initially and lets say in contains some user info and then upon some condition F2 pops on asking user to fill in additional attribute - their phone number. Next, you want that phone number to pop back to F1 and complete signup but you realize all previous user info is lost and you don't have their previous data. The fragment is recreated from scratch and even if you saved this information in onSaveInstanceState the bundle comes back null in onActivityCreated.
Solution:
Save required information as an instance variable in calling activity. Then pass that instance variable into your fragment.
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Bundle args = getArguments();
// this will be null the first time F1 is created.
// it will be populated once you replace fragment and provide bundle data
if (args != null) {
if (args.get("your_info") != null) {
// do what you want with restored information
}
}
}
So following on with my example: before I display F2 I save user data in the instance variable using a callback. Then I start F2, user fills in phone number and presses save. I use another callback in activity, collect this information and replace my fragment F1, this time it has bundle data that I can use.
#Override
public void onPhoneAdded(String phone) {
//replace fragment
F1 f1 = new F1 ();
Bundle args = new Bundle();
yourInfo.setPhone(phone);
args.putSerializable("you_info", yourInfo);
f1.setArguments(args);
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, f1).addToBackStack(null).commit();
}
}
More information about callbacks can be found here: https://developer.android.com/training/basics/fragments/communicating.html
Replace a Fragment using following code:
Fragment fragment = new AddPaymentFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.frame, fragment, "Tag_AddPayment")
.addToBackStack("Tag_AddPayment")
.commit();
Activity's onBackPressed() is :
#Override
public void onBackPressed() {
android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 1) {
fm.popBackStack();
} else {
finish();
}
Log.e("popping BACKSTRACK===> ",""+fm.getBackStackEntryCount());
}
Public void replaceFragment(Fragment mFragment, int id, String tag, boolean addToStack) {
FragmentTransaction mTransaction = getSupportFragmentManager().beginTransaction();
mTransaction.replace(id, mFragment);
hideKeyboard();
if (addToStack) {
mTransaction.addToBackStack(tag);
}
mTransaction.commitAllowingStateLoss();
}
replaceFragment(new Splash_Fragment(), R.id.container, null, false);
Perfect solution that find old fragment in stack and load it if exist in stack.
/**
* replace or add fragment to the container
*
* #param fragment pass android.support.v4.app.Fragment
* #param bundle pass your extra bundle if any
* #param popBackStack if true it will clear back stack
* #param findInStack if true it will load old fragment if found
*/
public void replaceFragment(Fragment fragment, #Nullable Bundle bundle, boolean popBackStack, boolean findInStack) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
String tag = fragment.getClass().getName();
Fragment parentFragment;
if (findInStack && fm.findFragmentByTag(tag) != null) {
parentFragment = fm.findFragmentByTag(tag);
} else {
parentFragment = fragment;
}
// if user passes the #bundle in not null, then can be added to the fragment
if (bundle != null)
parentFragment.setArguments(bundle);
else parentFragment.setArguments(null);
// this is for the very first fragment not to be added into the back stack.
if (popBackStack) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else {
ft.addToBackStack(parentFragment.getClass().getName() + "");
}
ft.replace(R.id.contenedor_principal, parentFragment, tag);
ft.commit();
fm.executePendingTransactions();
}
use it like
Fragment f = new YourFragment();
replaceFragment(f, null, boolean true, true);
Calling the Fragment lifecycle methods properly and using onSavedInstanceState() can solve the problem.
i.e Call onCreate(), onCreateView(), onViewCreated() and onSavedInstanceState() properly and save Bundle in onSaveInstanceState() and resotre it in onCreate() method.
I don't know how but it worked for me without any error.
If anyone can explain it will very much appreciated.
public class DiagnosisFragment extends Fragment {
private static final String TITLE = "TITLE";
private String mTitle;
private List mList = null;
private ListAdapter adapter;
public DiagnosisFragment(){}
public DiagnosisFragment(List list, String title){
mList = list;
mTitle = title;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null){
mList = savedInstanceState.getParcelableArrayList(HEALTH_ITEMS);
mTitle = savedInstanceState.getString(TITLE);
itemId = savedInstanceState.getInt(ID);
mChoiceMode = savedInstanceState.getInt(CHOICE_MODE);
}
getActivity().setTitle(mTitle);
adapter = (ListAdapter) new HealthAdapter(mList, getContext()).load(itemId);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.diagnosis_fragment, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ListView lv = view.findViewById(R.id.subLocationsSymptomsList);
lv.setAdapter(adapter);
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
outState.putParcelableArrayList(HEALTH_ITEMS, (ArrayList) mList);
outState.putString(TITLE, mTitle);
}
}
For who has looking for solution :
#Override
public void onDestroyView() {
Bundle savedState=new Bundle();
// put your data in bundle
// if you have object and want to restore you can use gson to convert it
//to sring
if (yourObject!=null){
savedState.putString("your_object_key",new Gson().toJson(yourObject));
}
if (getArguments()==null){
setArguments(new Bundle());
}
getArguments().putBundle("saved_state",savedState);
super.onDestroyView();
}
and in onViewCreated() method :
Bundle savedState=null;
if (getArguments()!=null){
savedState=getArguments().getBundle("saved_state");
}
if (savedState!=null){
// set your restored data to your view
}
Is there a way to check which activity loads a fragment from within the fragment? I have a user profile fragment which is loaded from 2 different activities but only one passes a value in, so when getting values from the bundle when the fragment loads from the activity that doesn't pass a value throws a nullpointerexception. Or is there a better way to pass the value into the fragment so the fragment can get the value only when loaded from this specific activity?
You can simply do this check in your fragment:
if(getActivity() instanceof SomeActivity){
//do something
}
You can also cast the getActivity() call if you are sure which Activity it is (and then call methods from that Activity).
You can define two static methods (also known as Static Factory Methods) which returns instances of your Fragment with the parameters you want to pass with bundle like the following code:
private static final String BUNDLE_VALUE_KEY = "value";
#NonNull
public static YourFragment newInstance() {
return new YourFragment();
}
#NonNull
public static YourFragment newInstance(#NonNull String yourValue) {
final YourFragment instance = new YourFragment();
final Bundle args = new Bundle();
args.putString(BUNDLE_VALUE_KEY,yourValue);
instance.setArguments(args);
return instance;
}
In your Activity which you want to pass the value you can use:
final YourFragment fragment = YourFragment.newInstance(value);
getSupportFragmentManager().beginTransaction()
.replace(R.id.framelayout, fragment)
.commit();
In other Activity you can use:
final YourFragment fragment = YourFragment.newInstance();
getSupportFragmentManager().beginTransaction()
.replace(R.id.framelayout, fragment)
.commit();
And in onCreate method of your Fragment you can check arguments and init your value like the following code:
final Bundle args = getArguments();
if(args != null){
yourValue = args.getString(BUNDLE_VALUE_KEY,null);
}
So if your Fragment is attached to the Activity which passes data you'll have it. If the Fragment is attached to other Activity you'll have the initial value (in the example it's null)
Or you can check the instance of Activity which Fragment gets attached. (Not best practice)
if(getActivity() instanceof YourActivityWhichPassesData){
yourValue = getArguments().getString(BUNDLE_VALUE_KEY,null);
}
This is how my application runs.
The application has several fragments as view pager connecting through main activity. From Second Fragment, I launched custom fragment dialog and upon ok button click inside that fragment dialog, I am fetching the result into main activity. I am using android.support.v4 library.
public class MainActivity extends FragmentActivity implements MyDialog.Communicator {
private static String callBackMessage;
#Override
public Fragment getItem(int pos) {
switch (pos) {
case 0:
return FirstFragment.newInstance();
case 1:
return SecondFragment.newInstance(MainActivity.this);
default:
return ThirdFragment.newInstance();
}
}
#Override
public void onDialogMessage(String callBackMessage) {
MainActivity.callBackMessage = callBackMessage;
}
public static String getCallBackMessage() {
return callBackMessage;
}
This is my MyDialog Fragment class, here I am creating some rows and giving checkboxes to be selected.
private void performOKButtonFunctionality() {
String msg = checkedBoxesCount + "";
communicator.onDialogMessage(msg);
SecondFragment secondFragment = new SecondFragment();
secondFragment.testFunction(row);
dismiss();
}
Now because of my very early days into android and its fragment life cycle, I am calling a method on Second fragment after instantiating it. the row parameter just passes the row number.
Now inside the Second Fragment Class I have some thing like,
public void testFunction(String callBackRow) {
callBackRowNo = Integer.parseInt(callBackRow);
String callBackMessage = MainActivity.getCallBackMessage();
getTableData(callBackMessage + "");
}
In order to save the SecondFragment previous state, I am saving its context by making it static like this,
private static Context activityContext;
public static SecondFragment newInstance(Context context) {
activityContext = context;
SecondFragment f = new SecondFragment();
Bundle b = new Bundle();
f.setArguments(b);
return f;
}
Now doing so, I am able to use my Second Fragment only one time after clicking the ok button from Fragment Dialog. but upon clicking again, the app crashes at this point.
FragmentManager myDialogManager = getActivity().getSupportFragmentManager();
The raised exception is:
java.lang.NullPointerException: Attempt to invoke virtual method 'android.support.v4.app.FragmentManager android.support.v4.app.FragmentActivity.getSupportFragmentManager()' on a null object reference
Now, my questions are:
How do we reenter into the oncreate method of the fragment where it just restores its previous state rather than saving it explicitly before launching
fragment dialog.
How does the fragment dialog pass parameter to the fragment which can be used to update the fragment field.
Is there some easy way to achieve this?
Before asking I searched the SO but could not find anything where this scenario was asked or explained.
I am badly stuck here and any solution/direction is much appreciated.
try getChildFragmentManager() instead of getSupportFragmentManager()
when do you make fragment with newInstace, you can put argsments like this.
public static MyFragment newInstance(int value) {
MyFragment f = new MyFragment();
Bundle args = new Bundle();
args.putInt("value", value);
f.setArguments(args);
return f;
}
it also needs constructor MyFragment()
public MyFragment() {}
get values from args like this
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (getArguments() != null) {
value = getArguments().getInt("value");
}
return inflater.inflate(R.layout.your_layout, container, false);
}
in my app I'm using one activity and two fragments. The app uses a layout with a container so the fragments are added via transactions. The first fragment contains a listview and the other fragment a detail view for the listview items.
Both fragments use setRetainInstance(true). The fragments are added via a replace transaction and addToBackStack(null) is set. The listfragment contains an instance variable which holds some infos for the list. Now I'm changing to detail and press back and the instance variable is null. I read about setRetainInstance and addToBackStack and removed addToBackStack, but even then the instance variable is null.
Does anyone know what I might be doing wrong?
regards,
Thomas
setRetainInstance(true) will tell the FragmentManager to keep the fragment around when the containing Activity is killed and rebuilt for some reason. It doesn't guarantee that the Fragment instance will stick around after a transaction to add or replace. It sounds like your adapter is being garbage collected and you're not creating a new one.
A more generally easy solution would be to make a viewless Fragment to retain your ListAdapter. The way you do this is to create the Fragment, set the retain instance to true, and return null in the method onCreateView(). To add it, just called addFragment(Fragment, String) via the FragmentTransaction. You never remove or replace it, so it will always stay in memory for the length of the app. Screen rotations won't kill it.
Whenever your ListFragment is created, in onCreateView() get the FragmentManager and use either the method findFragmentById() or FindFragmentByTag() to retrieve your retained fragment from memory. Then get the adapter from that fragment and set it as your adapter for the list.
public class ViewlessFragment extends Fragment {
public final static string TAG = "ViewlessFragment";
private ListAdapter mAdapter;
#Override
public ViewlessFragment() {
mAdapter = createAdater();
setRetainInstance(true);
}
#Override
public void onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return null;
}
public ListAdapter getAdapter() {
return mAdapter;
}
}
public class MyListFragment extends ListFragment {
final public static String TAG = "MyListFragment";
#Override
public void onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View returnView = getMyView();
final ViewlessFragment adapterFragment = (ViewlessFragment) getFragmentManager().findFragmentByTag(ViewlessFragment.TAG);
setListAdapter(ViewlessFragment.getAdapter());
return returnView;
}
}
public class MainActivity extends FragmentActivity {
#Override
public void onCreate(Bundle icicle) {
// ... setup code...
final FragmentManager fm = getSupportFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
ViewlessFragment adapterFragment = fm.findFragmentByTag(ViewlessFragment.TAG);
if(adapterFragment == null) {
ft.add(new ViewlessFragment(), ViewlessFragment.TAG);
}
ft.add(R.id.fragmentContainer, new MyListFragment(), MyListFragment.TAG);
ft.commit();
}
}
I have a ViewPager with two Fragments which I instantiate in onCreate of my FragmentActivity.
private List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this,Frag_1.class.getName()));
fragments.add(Fragment.instantiate(this,Frag_2.class.getName()));
this.vPagerAdapter = new Adapt(super.getSupportFragmentManager(),fragments);
vPager = (ViewPager) super.findViewById(R.id.pager);
vPager.setAdapter(vPagerAdapter);
My second Fragment has a method inside that I call to update my ListView - refreshList():
public class Frag_2 extends Fragment {
private ListView list;
private ArrayList<data> data;
private boolean firstCreation=true;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setRetainInstance(false);
}
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
super.onAttach(activity);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout, container, false);
list = (ListView) view.findViewById(R.id.lst);
//this.setRetainInstance(true);
return view;
}
public void refreshList(ArrayList <data> data){
if(data!=null){
ArrayAdapter<data> adapter = new Item_data_adapter(getActivity(),data);
list.setAdapter(adapter);}
}
}
Called from my FragmentActivity
//Something
Frag_2 fr = (Frag_2) vPagerAdapter.getItem(1);
if (fr.getView() != null) {
fr.refreshList(data);
}
It works fine until I change the orientation of the screen. Correct me if I'm wrong, but I was searching for hours and I didn't find a solution or a good explanation, the FragmentActivity is created only one time and the Fragments are attached to it but the Fragments recreate on configuration changes.
Now, when the orientation changes I don't get the View from onCreateso when I try to get the View from the Fragment it returns null and my refreshList() method isn't called. How can I fix this?
I fixed the problem this way:
In the onCreate of the FragmentActivity
if(savedInstanceState!=null){
frag1 = (frag_1) getSupportFragmentManager().getFragment(savedInstanceState, frag_1.class.getName());
frag2 = (frag_2) getSupportFragmentManager().getFragment(savedInstanceState, frag_2.class.getName());
}
else{
frag1 = (frag_1) Fragment.instantiate(this,frag_1.class.getName());
frag2 = (frag_2) Fragment.instantiate(this,frag_2.class.getName());
}
fragments.add(frag1);
fragments.add(frag2);
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, frag_1.class.getName(), frag1);
getSupportFragmentManager().putFragment(outState, frag_2.class.getName(), frag2);
}
Maybe it's not the best solution in the universe, but it looks like it works...
When u want to refresh the List do something like this :
public void setView() {
Frag_2 fr = (Frag_2) vPagerAdapter.getItem(1);
fragmentManager.beginTransaction().detach(fr).commit();
fragmentManager.beginTransaction().attach(fr).commit();
}
If you are using a dynamic fragment, you need to test first to prevent creating a second instance of a fragment.
To test whether the system is re-creating the activity, check whether the Bundle argument passed to your activity’s
onCreate() is null.
If it is non-null, the system is re-creating the activity. In this case, the activity automatically re-instantiates existing
fragments.
If it's null you can safely instantiate your dynamic fragment. For example:
public void onCreate(Bundle savedInstanceState) {
// ...
if (savedInstanceState != null) {
FragmentManager fragmentManager = getFragmentManager()
// Or: FragmentManager fragmentManager = getSupportFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}
}
The Fragment class supports the onSaveInstanceState(Bundle) method (but not the onRestoreInstanceState() method) in much the same way as the Activity class.
The default implementation saves the state of all the fragment’s views that have IDs.
You can override this method to store additional fragment state information.
If the system is re-creating the fragment from a previous saved state, it provides a reference to the Bundle containing that state to the onCreate(), onCreateView(), and onActivityCreated() methods; otherwise, the
argument is set to null.
If you want a detailed info, here's a good talk by Ken Jones of Marakana