In many websites i see that a fragment must always be embedded in an activity and the fragment's lifecycle is directly affected by the host activity's lifecycle - when the activity is paused, so are all fragments in it, and when the activity is destroyed, so are all fragments.
But, it's also written there that we can reuse fragment in different activities - but from above, if we move to another activity, the fragment will be destroyed. What i'm missing, or moreover, can someone give me an example of reuse same fragment in different activities?
I think you're getting confused with the concepts of Fragment implementation and Fragment instance. You can use the same Fragment implementation in different Activity, but for each Activity you need a new Fragment instance. The lifecycle of that instance is what will be directly affected by the host Activity's lifecycle.
Having a Fragment, let's call it FragmentA, and a couple Activity, let's call it ActivityA and ActivityB, you have 3 classes:
public FragmentA extends Fragment {
// All the FragmentA implementation
}
public ActivityA extends Activity {
// All the ActivityA implementation
}
public ActivityB extends Activity {
// All the ActivityB implementation
}
In this case, you could use the implementation of FragmentA in booth ActivityA and ActivityB, but for each case you'll need to create a new instance of FragmentA.
public ActivityA extends Activity {
loadFragmentA() {
FragmentA instanceA = new FragmentA();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_container, instanceA)
.commit();
}
}
public ActivityB extends Activity {
loadFragmentA() {
FragmentA instanceB = new FragmentA();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_container, instanceB)
.commit();
}
}
Like this, instanceA would be related to the lifecycle of ActivityA and instanceB would be related to the lifecycle of ActivityB, but booth are instances of FragmentA.
Reusing means that a certain Fragment (FragmentA) isn't tied to a certain Activity (ActivityA), but can be used by different Activities (ActivityB, ActivityC). It doesn't mean that you can pass a Fragment instance between Activities.
You are getting it little wrong. By reuse it means that we can use one fragment(definition) at multiple places rather than the fragment object itself. It doesn't mean that you can pass fragment instance between activities. For new activity, there is a new SupportFragmentManager/Manager. So, you have to create a new instance of the same fragment.
Fragment thus allows you to keep only one piece of code for different screens.
The key here is onAttach(Activity) method of Fragment which is called once the fragment is associated with the parent activity.
You create an instance of a fragment class for any activity you need to use the fragment in and use it in a fragment transaction.
DetailsFragment details = DetailsFragment.newInstance(index);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.details, details);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
If you want to reuse a fragment, you should conver the activity as a fragment.
You only can reuse a fragment of an existing fragment of the FragmentManager you want to do the transaction (of activity or fragment).
See this post from FragmentManager:
https://developer.android.com/reference/android/app/FragmentManager.html
The fragments are allowed in the BackStack if you call the method addToBackStack at transaction.
Related
I have found many instances of a similar question but no answer unfortunately meets my requirements.
I have many fragment inside an activity and an object that I transmit from one fragment to another. This object will be modified by each fragment I want that when I go back in the backstack I recover the previous fragment instance and the previous version of the object I'am tried with onSaveInstanceState but apparently when backing up in a fragment onSaveInstanceState is not called
override fun onSaveInstanceState(outState: Bundle) {
outState?.run {
putParcelable("PRODUCT",Product)
}
super.onSaveInstanceState(outState)
}
When you modify the fragment, you need to create a new one and .replace the old one.
public BlankFragment modifyFragment() {
int newVar = myVar; // your modified variable. Not the best example
// you need to create a new instance of the fragment
// everytime you modify your fragment so that you can replace the
// one you want to go back to later
BlankFragment blankFragment = BlankFragment.newInstance(++newVar);
getActivity().getSupportFragmentManager().beginTransaction()
.replace(R.id.a_main_fl_root, blankFragment) // When you click back press. It will restore the replaced fragment.
.addToBackStack(null)
.commit();
return blankFragment; // the new fragment is returned so you can replace it again and again.
// what I didn't include is how to get the instance of the replaced fragment when you click back press.
}
There's no need for savedInstanceState because it's only used for when the activity gets destroyed and recreated.
My application has a Fragment inside its Activity. I would like to programmatically replace the fragment by another one from the current fragment itself.
For example, if I click on a button inside the fragment, the fragment should be replaced with another one, but the activity should remain the same.
Is it possible? If so, how to do it?
It's actually easy to call the activity to replace the fragment.
You need to cast getActivity():
((MyActivity) getActivity())
Then you can call methods from MyActivity, for example:
((MyActivity) getActivity()).replaceFragments(Object... params);
Of course, this assumes you have a replaceFragments() method in your activity that handles the fragment replace process.
Edit: #ismailarilik added the possible code of replaceFragments in this code with the first comment below which was written by #silva96:
The code of replaceFragments could be:
public void replaceFragments(Class fragmentClass) {
Fragment fragment = null;
try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
// Insert the fragment by replacing any existing fragment
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.flContent, fragment)
.commit();
}
from the official docs:
// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
In this example, newFragment replaces whatever fragment (if any) is currently in the layout container identified by the R.id.fragment_container ID. By calling addToBackStack(), the replaced fragment is saved to the back stack so the user can reverse the transaction and bring back the previous fragment by pressing the Back button.
The behavior you have described is exactly what fragments are designed to do. Please go through the official guide for a thorough understanding of fragments which will clear up all your questions.
http://developer.android.com/guide/components/fragments.html
Please note that fragment should NOT directly replace itself or any other fragments. Fragments should be separate entities. What fragment should do is to notify its parent activity that some event has happened. But it is, again, NOT a fragment job to decide what to do with that! It should be activity to decide to i.e. replace the fragment on phone, but to i.e. add another to existing one on tablets. So you are basically doing something wrong by design.
And, as others already mentioned, your activity should use FragmentManager ("native" or from compatibility library) to do the job (like replace() or add() or remove()):
http://developer.android.com/guide/components/fragments.html
Just as Marcin said, you shouldn't have a fragment start another fragment or activity. A better way to handle this situation is by creating a callback implementation for the main activity to handle requests such as start a new fragment. Here is a great example in the android developer guide.
There is a way which works; Just (in the fragment) do the following:
getFragmentManager().beginTransaction()
.add(R.id. container_of_this_frag, new MyNewFragment())
.remove(this)
.commit();
When using nested fragments, we don't want every inner fragment replacement goes to the outer most activity. A mechanism allowing a fragment to notify its parent that it wants to change to another fragment can be useful.
Here is my code in Kotlin, I think it is easy to translate into java.
interface FragmentNavigator {
fun navigateTo(fragment: Fragment)
}
class NavigableFragment: Fragment() {
var navigator: FragmentNavigator? = null
override fun onDetach() {
super.onDetach()
navigator = null
}
}
Inner fragments need to extend NavigableFragment, and use following code to change itself to another fragment.
navigator?.navigateTo(anotherFragment)
Outer activities or fragments need to implement FragmentNavigator, and override navigateTo.
override fun navigateTo(fragment: Fragment) {
supportFragmentManager.beginTransaction().replace(view_id, fragment).commit()
}
//Use childFragmentManager instead of supportFragmentManager a fragment
Finally in outer activities or fragments, override onAttachFragment
override fun onAttachFragment(fragment: Fragment?) {
super.onAttachFragment(fragment)
if(fragment is NavigableFragment) {
fragment.navigator = this
}
}
This worked for me:
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
new MenuFragment()).commit();
For Kotlin.
(activity as YourActivityLauncherFragment)
.supportFragmentManager.beginTransaction()
.replace(R.id.yourFragmentContainer, YourFragmentName()).setReorderingAllowed(true)
.commit()
I'm having a little trouble understanding the behavior of fragments inside an activity. Consider the following scenario: I have a holder activity and 2 or more fragments inside.
The onCreate method for the activity is like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_holder);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction().add(R.id.container, new Frag1(), "ZZZ").commit();
}
}
I have a button in Frag1 which is linked to a callBack in the activity:
#Override
public void bam(String s) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction beginTransaction = fragmentManager.beginTransaction();
beginTransaction.replace(R.id.container, new Frag2());
beginTransaction.addToBackStack(null);
beginTransaction.commit();
}
At this point Frag2 is on the stack and the only visible Fragment. I used replace and addToBackStack because I need the back navigation.
My problem is that when I rotate the screen while inside Frag2, the super.onCreate(savedInstanceState) method from the activity calls the constructor for Frag1.
Is there any way to avoid the call to Frag1's constructor until the user presses the back button?
Fragments added to the backstack stay in memory and cannnot be garbage collected. They are kept as actual references to fragments. The reason it is recreated is because you still have an instance of the fragment. You can still call it's methods and fields as you can with any other object; it's simply not visible to the user and trying to manipulate its views may fail.
If the only purpose of adding the fragment to the backstack is navigation, this can be accomplished by not putting the fragment in the backstack to beging with, thus letting that instance of the fragment fall out of memory, then by overriding the onBackPressed() in the activity you can re-create() your fragment 1. You are free to cache any data you need as well.
The purpose of the backstack is to preserve the fragments state. When it's written to the backstack onDestroyView() is called, but it's viewHierarchy is saved with onSaveInstancestate(). This saves stuff like text in TextViews, scroll positions, etc.
If there's resource intensive stuff in Fragment 1's initialization you can also try moving it to a later lifecycle event, like onResume().
You can set properties for activity in manifest file so that your activity wont get destroyed on configuration change like as below :
<activity
android:name=".HomeActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustPan|adjustResize" >
</activity>
android:configChanges="keyboardHidden|orientation|screenSize" ; these are the properties.
Or you can do a cross check, by matching tags of fragment, while adding or replacing fragments.For this you need to code as mention below :
1) Adding tag while adding fragment :
getFragmentManager().beginTransaction().add(R.id.container, new Frag1(), "TAG NAME").commit();
2) Then check for existing fragment in onCreate() of activity as below :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_holder);
fragment1 = getSupportFragmentManager().findFragmentByTag("TAG NAME");
if(fragment1 == null) { //if fragment null, then add fragment
getFragmentManager().beginTransaction().add(R.id.container, new Frag1(), "TAG NAME").commit();
}
}
Orientation change from one fragment to another.
Orientation 1 (Landscape to Portrait):
onSaveInstanceState() of fragment 1.
onSaveInstanceState() of fragment 2.
onStop() of fragment 2.
onDestroy() of fragment 1.
onDetach() of fragment 1.
onAttach() of fragment 2.
onCreateView() of fragment 2.
onStart() of fragment 2.
Orientation 2 (Portrait to Landscape back):
onSaveInstanceState() of fragment 1.
onSaveInstanceState() of fragment 2.
onSaveInstanceState() of fragment 1.
onStop() of fragment 2.
onDestroy() of fragment 1.
onDetach() of fragment 1.
onDestroy() of fragment 1.
onDetach() of fragment 1.
onAttach() of fragment 2.
onCreateView() of fragment 2.
onStart() of fragment 2.
So as you notice that when I come back to fragment 1, the onSaveInstanceState(), onDestroy() and onDetach() are called two times for second orientation successive change.
Like that it keeps on increasing with every orientation change.
My activity code:
I am adding the fragment like this-
Fragment1 firstFragment = new Fragment1();
Bundle bundle = new Bundle();
firstFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.replace(R.id.article_fragment, firstFragment)
.addToBackStack(null).commit();
UPDATE:
So what is happening is whenever I click on any tab, the addToBackStack adds the tabbed fragment into the container decreasing the memory and calling repeated life cycle methods.
Any way to effectively check if the fragment already exist then remove previous and add the current one ?
NOTE:
I tried --
if(savedInstanceState == null) { /*Add fragment*/ }
Fragment1 fragment = (Fragment1) getSupportFragmentManager().findFragmentById(R.id.article_fragment);
//if (fragment == null) { /*Add fragment*/ }
These aren't clean solutions causing other problems.
Probable cause to your Lifecycle might be, not handling Fragment BackStack correctly. That's why it might be calling multiple times. I too have faced this issues with fragments. But for now you can remove adding your Fragment to backStack to proceed.
You code would become:
Fragment1 firstFragment = new Fragment1();
Bundle bundle = new Bundle();
firstFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.replace(R.id.article_fragment, firstFragment)
.commit();
Some examples where you can find handling AddingBackStack to a Fragment can be found at:http://www.javacodegeeks.com/2013/06/android-fragment-transaction-fragmentmanager-and-backstack.html
http://www.vogella.com/tutorials/AndroidFragments/article.html
And In order to retain the Fragments member values, you can use SharedPreference in Android to hold values and retrieve when activity is loaded.
http://developer.android.com/reference/android/content/SharedPreferences.html
My application has a Fragment inside its Activity. I would like to programmatically replace the fragment by another one from the current fragment itself.
For example, if I click on a button inside the fragment, the fragment should be replaced with another one, but the activity should remain the same.
Is it possible? If so, how to do it?
It's actually easy to call the activity to replace the fragment.
You need to cast getActivity():
((MyActivity) getActivity())
Then you can call methods from MyActivity, for example:
((MyActivity) getActivity()).replaceFragments(Object... params);
Of course, this assumes you have a replaceFragments() method in your activity that handles the fragment replace process.
Edit: #ismailarilik added the possible code of replaceFragments in this code with the first comment below which was written by #silva96:
The code of replaceFragments could be:
public void replaceFragments(Class fragmentClass) {
Fragment fragment = null;
try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
// Insert the fragment by replacing any existing fragment
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.flContent, fragment)
.commit();
}
from the official docs:
// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
In this example, newFragment replaces whatever fragment (if any) is currently in the layout container identified by the R.id.fragment_container ID. By calling addToBackStack(), the replaced fragment is saved to the back stack so the user can reverse the transaction and bring back the previous fragment by pressing the Back button.
The behavior you have described is exactly what fragments are designed to do. Please go through the official guide for a thorough understanding of fragments which will clear up all your questions.
http://developer.android.com/guide/components/fragments.html
Please note that fragment should NOT directly replace itself or any other fragments. Fragments should be separate entities. What fragment should do is to notify its parent activity that some event has happened. But it is, again, NOT a fragment job to decide what to do with that! It should be activity to decide to i.e. replace the fragment on phone, but to i.e. add another to existing one on tablets. So you are basically doing something wrong by design.
And, as others already mentioned, your activity should use FragmentManager ("native" or from compatibility library) to do the job (like replace() or add() or remove()):
http://developer.android.com/guide/components/fragments.html
Just as Marcin said, you shouldn't have a fragment start another fragment or activity. A better way to handle this situation is by creating a callback implementation for the main activity to handle requests such as start a new fragment. Here is a great example in the android developer guide.
There is a way which works; Just (in the fragment) do the following:
getFragmentManager().beginTransaction()
.add(R.id. container_of_this_frag, new MyNewFragment())
.remove(this)
.commit();
When using nested fragments, we don't want every inner fragment replacement goes to the outer most activity. A mechanism allowing a fragment to notify its parent that it wants to change to another fragment can be useful.
Here is my code in Kotlin, I think it is easy to translate into java.
interface FragmentNavigator {
fun navigateTo(fragment: Fragment)
}
class NavigableFragment: Fragment() {
var navigator: FragmentNavigator? = null
override fun onDetach() {
super.onDetach()
navigator = null
}
}
Inner fragments need to extend NavigableFragment, and use following code to change itself to another fragment.
navigator?.navigateTo(anotherFragment)
Outer activities or fragments need to implement FragmentNavigator, and override navigateTo.
override fun navigateTo(fragment: Fragment) {
supportFragmentManager.beginTransaction().replace(view_id, fragment).commit()
}
//Use childFragmentManager instead of supportFragmentManager a fragment
Finally in outer activities or fragments, override onAttachFragment
override fun onAttachFragment(fragment: Fragment?) {
super.onAttachFragment(fragment)
if(fragment is NavigableFragment) {
fragment.navigator = this
}
}
This worked for me:
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
new MenuFragment()).commit();
For Kotlin.
(activity as YourActivityLauncherFragment)
.supportFragmentManager.beginTransaction()
.replace(R.id.yourFragmentContainer, YourFragmentName()).setReorderingAllowed(true)
.commit()