I try to reduce my view hierarchy and use the android.R.id.content view to add a Fragment which use setRetainInstance( true ) to keep its instance alive.
My activity is very simple
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate( final Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
// ensure that the view is available if we add the fragment
findViewById( android.R.id.content ).post( new Runnable(){
#Override
public void run() {
// add the fragment only once to manager
if( savedInstanceState == null ) {
getSupportFragmentManager()
.beginTransaction()
.add( android.R.id.content, new LoginFragment() )
.commit();
}
}
} );
}
}
The Fragment create its own view and use the setRetainInstance(true) method in onCreate().
My problem is that after a orientation change my fragment isn't re-added to the activity and the activity is empty.
savedInstanceState may not be null after you rotate screen, so fragment did not add to the activity.
Although the fragment itself do not get killed, but activity get killed and you have to re-add the fragment to activity again.
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentWithTag("TAG");
if(fragment == null){
fragment = new LoginFragment();
}else{
fm.beginTransaction()
.add(android.R.id.content, fragment, "TAG")
.commit();
}
By the way, setRetainInstance(true) is not meant to use this way. You should allow fragment to get kill and re-create along with activity.
Related
I have simple activity and fragment transaction. What i noticed that on configuration changes oncreateView of Fragment is called twice. Why is this happening?
Activity Code Here :
#Override
protected void onCreate(Bundle savedInstanceState) {
System.out.println("Activity created");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager manager = getSupportFragmentManager();
BlankFragment fragment = new BlankFragment();
addFragmentToActivity(manager,
fragment,
R.id.root_activity_create
);
}
public static void addFragmentToActivity (FragmentManager fragmentManager,
Fragment fragment,
int frameId)
{
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(frameId, fragment);
transaction.commit();
}
Fragment Code Here :
public class BlankFragment extends Fragment {
public BlankFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank, container, false);
}
}
On first load onCreateView() is called once
But onRotation onCreateView() is called twice
why ?
Because of this transaction.replace(frameId, fragment); Really? Yes,I mean because of fragment .You already have one fragment onFirst load, When you rotate onCreate() will be called once again, so now fragment manager has old fragment ,so it methods will execute(once),and next you are doing transaction replace() which will remove old fragment and replace it with new once and again(onCreateView() will be called for second time). This is repeating for every rotation.
If you use transaction.add(frameId, fragment,UNIQUE_TAG_FOR_EVERY_TRANSACTION) you would know the reason. for every rotatation, no.of onCreateView() calls will increase by 1. that means you are adding fragments while not removing old ones.
But solution is to use old fragments.
in onCreate()of activity
val fragment = fragmentmanager.findFrgmentByTag("tag")
val newFragment : BlankFragment
if(fragment==null){
newFragment = BlankFragment()
}else{
newFragment = fragment as BlankFragment()
}
//use newFragment
Hope this solves confusion
Android automatically restores the state of its views after rotation. You don't have to call addFragmentToActivity again after rotation. The fragment will automatically be restored for you!
In your case, it happens twice because:
1. Android restores the fragment, its onCreateView is called
2. You replace the restored fragment with your own fragment, the oncreateview from that fragment is called too
do this:
if (savedInstanceState == null)
{
addFragmentToActivity(manager, fragment, R.id.test);
}
I'm using a widget called SwipeRefreshLayout, to refresh my fragment when someone pushes the view.
To recreate the activity I have to use:
SwipeRefreshLayout mSwipeRefreshLayout;
public static LobbyFragment newInstance() {
return new LobbyFragment();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_lobby, container, false);
receiver = new MySQLReceiver();
rlLoading = (RelativeLayout) view.findViewById(R.id.rlLoading);
gvLobby = (GridView) view.findViewById(R.id.gvLobby);
updateList();
mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.mSwipeRefreshLayout);
mSwipeRefreshLayout.setColorSchemeResources(R.color.pDarkGreen, R.color.pDarskSlowGreen, R.color.pLightGreen, R.color.pFullLightGreen);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
getActivity().recreate();
}
});
return view;
}
But I don't want to recreate the full activity that contains the view pager, I would like to recreate the fragment. How can I do that?
I'm using .detach() and .attach() for recreating the fragment.
ATTACH
Re-attach a fragment after it had previously been deatched from the UI with detach(Fragment). This causes its view hierarchy to be re-created, attached to the UI, and displayed.
DETACH
Detach the given fragment from the UI. This is the same state as when it is put on the back stack: the fragment is removed from the UI, however its state is still being actively managed by the fragment manager. When going into this state its view hierarchy is destroyed.
HOW I USE IT
getFragmentManager()
.beginTransaction()
.detach(LobbyFragment.this)
.attach(LobbyFragment.this)
.commit();
You can use :
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, LobbyFragment.newInstance()).commit();
To recreate your fragment
Note:getSupportFragmentManager() is if you are using support fragment and AppCompatActivity , if you are using framework fragment class you need to use getFragmentManager()
If you're using Navigation Component, you can use this:
findNavController().navigate(
R.id.current_dest,
arguments,
NavOptions.Builder()
.setPopUpTo(R.id.current_dest, true)
.build()
)
This lets NavController pop up the current fragment and then navigate to itself. You get a new Fragment and fragment ViewModel also gets recreated.
For Kotlin Lover
if you want to recreate fragment you should dettach() fragment then attach() fragment
please follow this step
setp : 1 , first create a method recreateFragment() on your activity class
fun recreateFragment(fragment : Fragment){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
supportFragmentManager.beginTransaction().detach(fragment).commitNow()
supportFragmentManager.beginTransaction().attach(fragment).commitNow()
}else{
supportFragmentManager.beginTransaction().detach(fragment).attach(fragment).commitNow()
}
}
step : 2 , then call this method on your fragment to recreate this fragment
suppose A Button click then recreate this fragment
button.setOnClickListener {
(activity as yourActivity).recreateFragment(this)
}
If you want to refresh from activity then use:
getSupportfragmentmanager()
.begintransaction
.detach(fragment)
.attach(fragment)
.addtobackstack(null)
.commit();
and if you are in fragment already then use:
public class MyDetailFragment extends Fragment {
....
private void refreshFragment(){
getFragmentManager()
.beginTransaction()
.detach(this)
.attach(this)
.addToBackStack(null)
.commit();
}
...
}
who use Navigation Component !! :
just put a self destination .
<action
android:id="#+id/action_piecesReferenceCount_self"
app:destination="#id/piecesReferenceCount" />
Navigation.findNavController(myview).navigate(R.id.action_piecesReferenceCount_self);
Using the method from Ciardini I got errors sometimes. This works always:
FragmentTransaction ft = getFragmentManager().beginTransaction();
if (Build.VERSION.SDK_INT >= 26) {
ft.setReorderingAllowed(false);
}
ft.detach(this).attach(this).commitAllowingStateLoss();
I had to use 2 transactions for the fragment to reload its content list:
FragmentTransaction ftr = getSupportFragmentManager().beginTransaction();
ftr.detach(localCurrentPrimaryItem)
.commit();
FragmentTransaction ftr2 = getSupportFragmentManager().beginTransaction();
ftr2.attach(localCurrentPrimaryItem)
.commit();
In my case, I had a fragment that needed to be recreated when I clicked on a button, what I did was the following in the onCreateView of the fragment (MyFragmentClass) class:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
Button annuler = (Button) rootView.findViewById(R.id.buttonAnnulerCreation);
annuler.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getParentFragmentManager().beginTransaction().replace(R.id.fragmentLayout, new MyFragmentClass()).commit();
}
}); }
Define a Kotlin extension function:
/**
* Recreate a fragment without recreating any associated fragment view model. This is useful if initially some work needs
* to be done to set up the data for a fragment. At the start the layout shows a "working" fragment state. When the work completes
* the fragment view model is set to indicate the data is available, and this triggers a different layout to be inflated.
*
* This causes [Fragment.onDestroy] followed by [Fragment.onViewCreated] to be called (but not [Fragment.onCreate]).
*
* For background see [Stackoverflow](https://stackoverflow.com/questions/39296873/how-can-i-recreate-a-fragment)
*/
fun Fragment.recreateFragment() {
val fragment = this
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
parentFragmentManager.beginTransaction().detach(fragment).commitNow()
parentFragmentManager.beginTransaction().attach(fragment).commitNow()
} else {
parentFragmentManager.beginTransaction().detach(fragment).attach(fragment).commitNow()
}
}
What I need is exactly an onResume method (as it works for activities) for a specific fragment. I'm adding the fragment (let's say fragment A) to the back stack, and opening another fragment (fragment B) (again adding to back stack) from fragment A. I want to update toolbar when fragment B is closed and fragment A is on screen again. I expect onCreateView to get called but it's not getting called when I pop fragment B. I also tried adding an OnBackStackChangedListener to fragment A but then I cannot track which fragment is on the screen when the back stack changes.
So my question is how to make onCreateView get called when I turn back to fragment A. And if this is not a good practice, how else can I track this event?
Edit
I'm showing new fragments with this code:
getSupportFragmentManager().beginTransaction()
.add(R.id.content, fragment)
.addToBackStack(tag)
.commit();
Should I change it somehow to make onCreateView get called? Since I'm adding new fragment B on existing fragment A (I can even click on a button which is in fragment A when B is on the screen), when I pop fragment B, nothing changes with fragment A's situation.
Override this method in the Fragment and check the boolean value
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
//Log.e("setUserVisibleHint", "isVisibleToUser " + isVisibleToUser);
}
Put the code that you need to be executed whenever the fragment becomes visible/is hidden in this method, according to the isVisibleToUser boolean value
Did you try OnBackStackChangedListener this way?
public class BlankFragment2 extends Fragment {
public BlankFragment2() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(getFragmentManager()==null)
return;
Fragment fr = getFragmentManager().findFragmentById(R.id.container)//id of your container;
if (fr instanceof BlankFragment2) {
//On resume code goes here
}
}
});
return inflater.inflate(R.layout.fragment_blank_fragment2, container, false);
}
}
I hope this solution will works.
1) Put/call addOnBackStackChangedListener on your Activity
getSupportFragmentManager().addOnBackStackChangedListener(backStacklistener);
2) Define backStacklistener inside your Activity
FragmentManager.OnBackStackChangedListener backStacklistener = new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager manager = getSupportFragmentManager();
if (manager != null) {
Fragment fragment = manager.findFragmentById(R.id.fragment);
if(fragment instanceof OutboxFragment) {
OutboxFragment currFrag = (OutboxFragment) fragment;
currFrag.onFragmentResume();
}
}
}
};
3) Provide a method on your fragment that you want to be triggered. In this case I create method named onFragmentResume()
public void onFragmentResume() {
MainActivity activity = (MainActivity) getActivity();
activity.showFab();
// or do another thing here
}
Good luck!
My fragment is being created twice, even though the activity is only adding the fragment once to the content. This happens when I rotate the screen. Also, everytime the fragment's onCreateView is called, it has lost all of it's variable state.
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) { // Checking for recreation
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new AppPanelFragment())
.commit();
}
}
}
onCreate in the activity checks for the null savedInstanceState and only if null will add the fragment so I can't see why the fragment should be created twice? Putting a breakpoint in that if condition tells me that it only ever get's called once so the activity shouldn't be adding the fragment multiple times. However the onCreateView of the fragment still gets called each time orientation changes.
public class AppPanelFragment extends Fragment {
private TextView appNameText;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// This method called several times
View rootView = inflater.inflate(R.layout.fragment_app_panel, container, false);
// 2nd call on this method, appNameText is null, why?
appNameText = (TextView) rootView.findViewById(R.id.app_name);
appNameText.text = "My new App";
return view;
}
I managed to have the variable state persisted using setRetainInstance(true), but is this the real solution? I would expect the fragment to not be created on just an orientation change.
In android, when the phone's orientation is changed, the activity is destroyed and recreated. Now, i believe to fix your problem you can use the fragment manager to check to see if the fragment already exists in the back stack and if it doesn't then create it.
public void onCreated(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
mFragmentManager = getSupportFragmentManager();
AppPanelFragment fragment = (AppPanelFragment)mFragmentManager.findFragmentById(R.id.fagment_id);
if(fragment == null) {
//do your fragment creation
}
}
}
P.S. I haven't tested this but it should work once you provide the right fragment's id in the findFragmentById method.
The Fragment lifecycle is very similar to an Activity. By default, yes, they will be re-created during a configuration change just like an Activity does. That's expected behavior. Even with setRetainInstance(true) (which I would say to use with extreme caution if it contains a UI) your View will be destroyed and re-created, but in that case your Fragment instance will not be destroyed -- just the View.
I know it is a bit late to answer, but using The Code Pimp answer you can do the next thing:
If the fragment exists in the backstack we pop and remove it to add it back (an exception is thrown if it is added back without removing it, saying it already exists).
The fragment variable is a class member variable.
This method will be called in the onCreate method of the Activity:
if (savedInstanceState == null) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (fragmentManager.findFragmentById(getFragmentActivityLayoutContainerId()) == null) {
fragment = getNewFragmentInstance();
} else {
fragment = fragmentManager.findFragmentById(getFragmentActivityLayoutContainerId());
fragmentTransaction.remove(fragment);
fragmentManager.popBackStack();
fragmentTransaction.commit();
fragmentTransaction = fragmentManager.beginTransaction();
}
fragmentTransaction.add(getFragmentActivityLayoutContainerId(), fragment);
fragmentTransaction.commit();
}
The next code will be called in the fragment itself.
It is a small example for a code you could implement in your fragment to understand how it works. The dummyTV is a simple text view in the center of the fragment that receives text according to orientation (and for that we need a counter).
private TextView dummyTV;
private static int counter = 0;
#Override
protected int getFragmentLayoutId() {
return R.layout.fragment_alerts_view;
}
#Override
protected void saveReferences(View view) {
dummyTV = (TextView) view.findViewById(R.id.fragment_alerts_view_dummy_tv);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
if (savedInstanceState != null) {
dummyTV.setText(savedInstanceState.getString("dummy_string"));
} else {
dummyTV.setText("flip me!");
}
dummyTV.append(" | " + String.valueOf(counter));
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putString("dummy_string", counter++ % 2 == 0 ? "landscape" : "portrait");
}
As mentioned, on orientation change, the activity is destroyed and recreated. Also, Fragments(any) are recreated by the system.
To ensure your application restores to previous state, onSaveInstanceState() is called before the activity is destroyed.
So, you can store some information in the onSaveInstanceState() method of an activity and then restore your application to same state on orientation change.
NOTE: You need not create fragments on orientation change, as fragments are recreated.
Example from http://www.mynewsfeed.x10.mx/articles/index.php?id=15:
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ( savedInstanceState == null ){
//Initialize fragments
Fragment example_fragment = new ExampleFragment();
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.container, example_fragment, "Example");
} else{
//control comes to this block only on orientation change.
int postion = savedInstanceState.getInt("position"); //You can retrieve any piece of data you've saved through onSaveInstanceState()
//finding fragments on orientation change
Fragment example_fragment = manager.findFragmentByTag("Example");
//update the fragment so that the application retains its state
example_fragment.setPosition(position); //This method is just an example
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("position", position); //add any information you'd like to save and restore on orientation change.
}
}
I have developed an app in Honeycomb and I am using fragments.
This is my app
I have an Activity (Say A1) and in that there is a fragment
Initially this fragment hold the object one fragment object say (F1)
Then depending on the user actions it may change to other objects F2,F3 ....
What my problem is
When The user rotate the device the activity is recreated and which make F1 as the fragment object even though before rotating it wasn't
What is the way to retain the fragment object while rotating?
I used setRetainInstance(true); but it didn't work for me
And I have added the fragment by code in my onCreate function like this
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
Fragment homeFragment = new Home();
fragmentTransaction.add(R.id.mainFragement, homeFragment);
fragmentTransaction.commit();
}
By default Android will retain the fragment objects. In your code you are setting the homeFragment in your onCreate function. That is why it is allways some homeFragment or fl what ever that you set in onCreate.
Because whenever you rotate, the onCreate will execute and set your fragment object to the first one
So the easy solution for you is check whether savedInstanceState bundle is null or not and set the fragment object
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(null == savedInstanceState) {
// set you initial fragment object
}
}
You need to give your Fragment a unique tag, and check whether this Fragment is already added to your Activity already or not.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String tag = "my_fragment";
FragmentManager fragmentManager = getFragmentManager();
if(fragmentManager.findFragmentByTag(tag) == null) {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
Fragment homeFragment = new Home();
fragmentTransaction.add(R.id.mainFragement, homeFragment, tag);
fragmentTransaction.commit();
}
}
Checking whether savedInstanceState is null is not a safe way to check whether your fragment is already set - it will work in most cases, but in some cases (such as when the device is on low memory), Android may kill your Activity, which could break your application.
To see this in action, tick "Don't keep activities" in the device's development options (the setting is available in Android 4.0+, not sure about earlier versions). When you open a new activity, your first activity is destroyed. When you return to it (by pressing back), it is created again, and savedInstanceState is not null. However, your fragment is not in the activity anymore, and you have to add it again.
EDIT - Showing the original principle but with SupportFragmentManager
public class ActivityAwesome extends AppCompatActivity
{
private final String TAG = getClass().getSimpleName();
private FragmentHome mHomeFragment;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(TAG);
if(fragment == null)
{
// Create the detail fragment and add it to the activity using a fragment transaction.
mHomeFragment = new FragmentHome();
fragmentManager.beginTransaction()
.add(R.id.fragment_container, mHomeFragment, TAG)
.commit();
}
else
{
// get our old fragment back !
mHomeFragment = (FragmentHome)fragment;
}
}
}
this comes in especially useful if you want to manipulate the fragment (in this case mHomeFragment) after rotating your device
Use onAttachFragment() in your Activity to reassign the object:
#Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof MyFragment)
this.myFragment = (MyFragment) fragment;
}
I defined a Fragment in activity's layout, onSaveInstanceState in the Fragment does get called, but the savedInstanceState Bundle in the Fragment's onCreatView comes as null.
The reason was that my Fragment did not have a ID in XML:
android:id="#+id/compass_fragment" ...
just rewiring #Ralf answer to be more dynamic, no need to specify a certain fragment to retain, but in case you want to specify, it is also possible :
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//set Home/Main/default fragment
changeFragmentTo(HomeFragment.newInstance(), FRAGMENT_TAG_HOME_FRAGMENT);
if (getCurrentFragment() != null) {
//if screen rotated retain Fragment
changeFragmentTo(getCurrentFragment(), getCurrentFragment().getTag());
}
}
private Fragment getCurrentFragment() {
//fl_main_container is FarmeLayout where I load my Fragments
return getSupportFragmentManager().findFragmentById(R.id
.fl_main_container);
}
/**
* changeFragmentTo(Fragment fragmentToLoad, String fragmentTag)
*
* #param fragmentToLoad : dataType > v4.app.Fragment :: the object of the fragment you want to load in form of MyFragment() or MyFragment().newInstance()
* #param fragmentTag : dataType > String :: a String which identify the "tag" of the fragment in form of "FRAGMENT_TAG_MY_FRAGMENT", Value must be stored in {#link models.MyConstants}
*/
public void changeFragmentTo(Fragment fragmentToLoad, String fragmentTag) {
if (getSupportFragmentManager().findFragmentByTag(fragmentTag) == null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fl_main_container, fragmentToLoad, fragmentTag)
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.addToBackStack(fragmentTag)
.commit();
} else {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fl_main_container, fragmentToLoad, fragmentTag)
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit();
}
}
}
You can simply set the RetainInstance property inside OnCreate of the fragment class.
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
RetainInstance = true;
}
Retain the Fragment object while rotating