Not understanding how setRetainInstance (or RetainInstance in Xamarin Android) works? - android

If you have some Fragment declared right inside your XML layout file, then looks like SetRetainInstance works expectedly.
However what if the Fragment is loaded dynamically into some container in the OnCreate callback of the main Activity? I don't see how setRetainInstance means or can do in this case.
Check out this code:
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
var f = new SomeFragment();
var ft = FragmentManager.BeginTransaction();
ft.Replace(Android.Resource.Id.Content, f);
ft.Commit();
}
You can see that everytime the OnCreate is called (such as when the screen is rotated), a new SomeFragment is created and fills the main content of the Activity.
Now even if I declare a variable (field) holding reference to the instance of fragment and create it only when it's null, something like this:
SomeFragment f;
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
if(f == null) {
f = new SomeFragment(this);
f.RetainInstance = true;
}
var ft = FragmentManager.BeginTransaction();
ft.Replace(Android.Resource.Id.Content, f);
ft.Commit();
}
This also does not work, everytime the Activity is created, the field f is still null initially (not retained). The only solution I found working here is declare the field f as static, like this:
static SomeFragment f;
With that I don't even need to use RetainInstance (or setRetainInstance in Java). In fact the requirement of loading Fragments dynamically is very popular, so at that point RetainInstance seems to be less helpful?
Or I missed something simple here to still take advantage of RetainInstance? The problem is use RetainInstance and dynamically load Fragments, if you have some solution or pattern to use here, please share with me, thanks!

Although I am not familiar to Xamarin, I can answer this in Java.
Instead of using
replace(int containerViewId, Fragment fragment)
You should use
replace(int containerViewId, Fragment fragment, String tag)
For example,
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ExampleFragment fragment;
fragment = (ExampleFragment) fragmentManager.findFragmentByTag("tag");
if(fragment == null)
fragment = new ExampleFragment();
fragmentTransaction.replace(R.id.fragment_container, fragment, "tag");
fragmentTransaction.commit();
But you should only use setRetainInstance if you are running longe task at background. You may have a look at this blog post. Recreating the fragment is more suggested as it can avoid unexpected behavior (e.g. not initializing some variables).

Related

Make a Fragment's Lifecycle Mimic Another

WHAT I HAVE
I have a Fragment called MainFragment. This is added from my MainActivity. Within that Fragment in on its onResume method I add another Fragment called SecondFragment like so.
#Override
public void onResume() {
super.onResume();
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
SecondFragment secondFragment = (SecondFragment) fragmentManager.findFragmentByTag(SecondFragment.FRAG_TAG);
if (secondFragment == null)
secondFragment = SecondFragment.newInstance();
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.add(mContainer.getId(), secondFragment, SecondFragment.FRAG_TAG).commit();
}
I then remove it like so.
#Override
public void onPause() {
super.onPause();
FragmentManager fm = getActivity().getSupportFragmentManager();
SecondFragment mhpf = (SecondFragment) fm.findFragmentByTag( SecondFragment.FRAG_TAG );
if ( mhpf != null )
fm.beginTransaction().remove( mhpf ).commit();
}
THE ISSUE
This all works well and good but it doesn't fit in with the lifecycle of the Fragment when things like orientation changes happen or when the user goes home and then opens the application up again.
WHY I'M DOING IT LIKE THIS AND NOT ADDING IT FROM THE ACTIVITY
This is part of an Android Library Module so I want it self contained. The user just has to add the MainFragment and then the SecondFragment is handled entirely by the MainFragment.
MY QUESTION
Is there a way to get my SecondFragment to behave like a normal Fragment without having to add it and remove it from and Activity?
Use getActivity().getChildFragmentManager(); in place of getActivity().getSupportFragmentManager();
If you are using Fragment inside Fragment.

GetFragmentManager.findFragmentByTag() returns null

getFragmentManager().beginTransaction()
.replace(R.id.graph_fragment_holder, new GraphFragment(), "GRAPH_FRAGMENT")
.commit();
getFragmentManager().beginTransaction()
.replace(R.id.list_fragment_holder, new ListFragment(), "LIST_FRAGMENT")
.commit();
//getFragmentManager().executePendingTransactions();
GraphFragment graphFragment = (GraphFragment) getFragmentManager().findFragmentByTag("GRAPH_FRAGMENT");
graphFragment.setData(data);
ListFragment listFragment = (ListFragment) getFragmentManager().findFragmentByTag("LIST_FRAGMENT");
listFragment.setData(data);
I've supplied a tag so I'm not sure why findFragmentByTag() returns null.
What I've tried from reading other questions:
this.setRetainInstance(true) in the oncreate of both fragments.
Both fragment constructors are empty public fragmentName(){}.
tried executePendingTransactions after adding the fragments.
tried add instead of replace on the fragments (edited)
I was confused about this for a long time. First, you need to save the fragment you are replacing by pushing it onto the back stack. The tag you supply is put on the fragment you are adding, not the one you are pushing onto the back stack. Later, when you do push it onto the back stack, that tag goes with it. Here's code with objects broken out to make it easier to trace. You must call 'addToBackStack' before 'commit'.
GraphFragment grFrag = new GraphFragment();
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
tr.replace(R.id.fragment_container, grFrag, "GRAPH_FRAGMENT");
// grFrag is about to become the current fragment, with the tag "GRAPH_FRAGMENT"
tr.addToBackStack(null);
// 'addToBackStack' also takes a string, which can be null, but this is not the tag
tr.commit();
// any previous fragment has now been pushed to the back stack, with it's tag
ListFragment liFrag = new ListFragment();
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
tr.replace(R.id.fragment_container, liFrag, "LIST_FRAGMENT");
// liFrag is is about to become the current fragment, with the tag "LIST_FRAGMENT"
tr.addToBackStack(null);
tr.commit();
// 'grFrag' has now been pushed to the back stack, with it's tag being "GRAPH_FRAGMENT"
Call getFragmentManager().executePendingTransactions() after fragment transaction.
getFragmentManager()
.beginTransaction()
.replace(R.id.container, new ExampleFragment(), "YOUR TAG HERE");
.commit();
//after transaction you must call the executePendingTransaction
getFragmentManager().executePendingTransactions();
//now you can get fragment which is added with tag
ExampleFragment exampleFragment = getFragmentManager().findFragmentByTag("YOUR TAG HERE");
I was having the same problem of findFragmentByTag() always returning null.
Eventually I tracked it down, I was overriding onSaveInstanceState() in my Activity but not calling super. As soon as I fixed that findFragmentByTag() returned the Fragment as expected.
You can use
fragmentTransaction.addToBackStack(yourFragmentTag);
After that you can reuse it with
getSupportFragmentManager().findFragmentByTag(yourFragmentTag);
Answered here, just need to call getSupportFragmentManager().executePendingTransactions(); after your findByTag or findById
In my case I had to create a class level FragmentManager object and then use it instead of using getSupportFragmentManager() directly.
public class Main extends BaseActivity {
FragmentManager fragmentManager;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragmain);
fragmentManager = getSupportFragmentManager();
initFrag1();
}
private void initFrag1() {
String name = Frag1.class.getSimpleName();
if (fragmentManager.findFragmentByTag(name) == null) {
fragmentManager.beginTransaction()
.add(R.id.frag_container, new Frag1(), name)
.addToBackStack(name)
.commit();
}
}
}

Remove old Fragment from fragment manager

I'm trying to learn how to use Fragments in android.
I'm trying to remove old fragment when new fragment is calling in android.
You need to find reference of existing Fragment and remove that fragment using below code. You need add/commit fragment using one tag ex. "TAG_FRAGMENT".
Fragment fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAGMENT);
if(fragment != null)
getSupportFragmentManager().beginTransaction().remove(fragment).commit();
That is it.
I had the same issue. I came up with a simple solution. Use fragment .replace instead of fragment .add. Replacing fragment doing the same thing as adding fragment and then removing it manually.
getFragmentManager().beginTransaction().replace(fragment).commit();
instead of
getFragmentManager().beginTransaction().add(fragment).commit();
I had the same issue to remove old fragments. I ended up clearing the layout that contained the fragments.
LinearLayout layout = (LinearLayout) a.findViewById(R.id.layoutDeviceList);
layout.removeAllViewsInLayout();
FragmentTransaction ft = getFragmentManager().beginTransaction();
...
I do not know if this creates leaks, but it works for me.
Probably you instance old fragment it is keeping a reference. See this interesting article Memory leaks in Android — identify, treat and avoid
If you use addToBackStack, this keeps a reference to instance fragment avoiding to Garbage Collector erase the instance. The instance remains in fragments list in fragment manager. You can see the list by
ArrayList<Fragment> fragmentList = fragmentManager.getFragments();
The next code is not the best solution (because don´t remove the old fragment instance in order to avoid memory leaks) but removes the old fragment from fragmentManger fragment list
int index = fragmentManager.getFragments().indexOf(oldFragment);
fragmentManager.getFragments().set(index, null);
You cannot remove the entry in the arrayList because apparenly FragmentManager works with index ArrayList to get fragment.
I usually use this code for working with fragmentManager
public void replaceFragment(Fragment fragment, Bundle bundle) {
if (bundle != null)
fragment.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment oldFragment = fragmentManager.findFragmentByTag(fragment.getClass().getName());
//if oldFragment already exits in fragmentManager use it
if (oldFragment != null) {
fragment = oldFragment;
}
fragmentTransaction.replace(R.id.frame_content_main, fragment, fragment.getClass().getName());
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragmentTransaction.commit();
}
I had issue with fragment onBackPress. I didn't want to return to old fragments.
This solution worked for me to remove old fragments from fragment manager :
FragmentManager fm = getSupportFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
You can also use an if block like this :
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
In Kotlin
val fragList = f.supportFragmentManager.fragments
for (fragment in fragList) {
f.supportFragmentManager.beginTransaction().remove(fragment).commit()
}
I was facing the same error with few views so I set the visibility of reductant views as GONE. This method solved my issue as charm.
<View>.setVisibility(View.GONE);

Can fragment be created with only one instance

I was just wondering, can fragment creation only have one instance or singleton?
I went through Google iosched project too. They simply create
Fragment a = new Fragment();
Whenever they want...
Suppose eg:
public static FragmentManager instance;
public static FragmentManager getInstance() {
if (instance == null) {
instance = new FragmentManager();
}
return instance;
}
public TestFragment getTestFragment() {
if (testFragment == null) {
testFragment = new TestFragment ();
}
return testFragment
}
}
Can I use everywhere
FragmentManager.getInstance().getTestFragment() for transaction?
eg:
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.content_frame, FragmentManager.getInstance().getTestFragment())
.commit();
Or OS automatically destroy the reference or some issues related to it?
When you use getSupportFragmentManager().beginTransaction().replace you can add a third parameter as a string that you can use as a tag, so in case you want to recover a previous fragment you can use getSupportFragmentManager().findFragmentByTag(String) so you won't have to create a new fragment.
So it would be like this
Check if the fragment exists using findFragmentByTag(String) if it not exists, create a new fragment and call getSupportFragmentManager().beginTransaction() .replace(R.id.content_frame,myFragment,myTag).commit(); where myTag is the String you'll use in your findFragmentByTag. This way you won't create more than one fragment of every type.
I hope it makes some sense :)
For more information check this and this
No such limitation. Though, two fragment objects must not have same tag or id.
Also, its good to re-attach an existing fragment, rather that creating a new one.
MyFragment f = (MyFragment) getFragmentManager().findFragmenByTag("my_fragment");
if(f == null){
f = Fragment.instantiate(context, MyFragment.class.getName());
}
if(!f.isAdded()){
//--do a fragment transaction to add fragment to activity, WITH UNIQUE TAG--
//--Optionally, add this transaction to back-stack as well--
}
If you are trying to make sure that you will not add or replace one or more of your fragments with the same "type" twice or more, then you can use the FragmentManager.BackStackEntry to know which of your fragments is currently on the top of the stack.
String TAG_FIRST_FRAGMENT = "com.example.name.FIRST.tag";
String TAG_SECOND_FRAGMENT = "com.example.name.SECOND.tag";
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() == 0 ||
!fragmentManager.getBackStackEntryAt(
fragmentManager.getBackStackEntryCount() - 1)
.getName().equals(TAG_SECOND_FRAGMENT)) {
//Now it's safe to add the secondFragment instance
FragmentTransaction transaction = fragmentManager.beginTransaction();
//Hide the first fragment if you're sure this is the one on top of the stack
transaction.hide(getSupportFragmentManager().findFragmentByTag(TAG_FIRST_FRAGMENT));
SecondFragment secondFragment = new SecondFragment();
transaction.add(R.id.content_frame, secondFragment, TAG_SECOND_FRAGMENT);
//Add it to back stack so that you can press back once to return to the FirstFragment, and
//to make sure not to add it more than once.
transaction.addToBackStack(TAG_SECOND_FRAGMENT);
transaction.commit();
}

How to pass data into fragment dynamically loaded into current activity

My activity contains "FrameLayout" element only.
In its 'onCreate' method I dynamically load fragment:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
UsersListFragment fragment = new UsersListFragment();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.fragment_container, fragment);
ft.commit();
}
On some event my fragment communicates back to activity and I need to instantiate another fragment and pass some data to it. I do it in the activity by implementing special interface that is in turn called by fragment. It looks like this way:
#Override
public void onUserSelected(long userId) {
UserDetailsFragment fragment = new UserDetailsFragment();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_container, fragment);
ft.addToBackStack(null);
ft.commit();
fragment.updateUser(userId);
}
As a result, "fragment.updateUser(userId);" is called way to early (fragment is not initialized yet, it is not attached to activity).
Question: what is the proper way to pass data into the fragment?
Thanks a lot in advance. Any thoughts are greatly appreciated.
P.S. I've seen a lot of examples that show how to load fragments, but was not able to find those that show how to pass data...
That usually happens to me: once wrote a question I got a new idea to try...
Looks like one of the possible options is to pass data by setting them via 'setArguments' method... and use during 'onCreateView' fragment method...
Any better options?

Categories

Resources