2

I'm working on a TabHost whose tabs have associated a Fragment (each tab a different one). Each of those Fragments have inside an instance of another Fragment which is a login bar that has two states: Logged in or not.

Logged out example

Logged out

Logged in example

Logged in

In layout terms, each of these states have associated a View (a TextView for the non-logged-in case and a LinearLayout for the logged-in case), so if one of them is VISIBLE, the other one is GONE. As per the tab content, this is an example of the code of one of them (firsttab.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:ads="http://schemas.android.com/apk/lib/com.google.android.gms.ads"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="#0000FF"
  android:orientation="vertical">

  <!-- That's the login bar -->
  <fragment 
    android:id="@+id/firsttab_loginrow"
    class="com.mydomain.myproject.LoginRowFragment"
    android:tag="1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

  <LinearLayout
    android:id="@+id/firsttab_content"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="10dp"
    android:gravity="center"
    android:orientation="horizontal">
  </LinearLayout>
</LinearLayout>

The inner Fragment (com.mydomain.myproject.LoginRowFragment) is defined this way:

<!-- If the user is not logged in -->
<TextView
   android:id="@+id/identification_nologin"
   android:layout_width="wrap_content"
   android:layout_height="match_parent"
   android:textColor="#FFFFFF" />

<!-- if the user is logged in -->
<LinearLayout
   android:id="@+id/identification_didlogin"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:orientation="horizontal" >

   ...      
</LinearLayout>

A general schema would be something like this:

Schema

The problem comes when I'm handling the tab change event by either attach()ing or detach()ing the correspondent parent Fragment (in this case, firsttab.xml). Previously to attaching/detaching the parent, I try detach()ing the login Fragment (the inner one), but it doesn't fire the onDetach() callback. The same happens when attach()ing. The weird thing is that if I replace .detach() with .remove(), it works just fine.

@Override
public void onTabChanged(final String tag) {
  final TabInfo newTab = mTabInfo.get(tag);

  if (lastTab != newTab) {

    final FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction();
    if ((lastTab != null) && (lastTab.getFragment() != null)) {

      // This is where it fails detaching. tabInfo is just a structure where I store some data
      // about the tabs to handle them. If I use remove() instead of the next detach() on loginFrag,
      // the onAttach()/onDetach() callbacks are not called. I'm quite sure everything is ok, loginFrag
      // is not null, it's actually the Fragment that should be detached, etc.

      final Fragment loginFrag = (Fragment) lastTab.getFragment().getActivity().getSupportFragmentManager().findFragmentById(lastTab.getLoginFragId());
      ft.detach(loginFrag);

      ft.detach(lastTab.getFragment());
    }

    if (newTab != null) {
      if (newTab.getFragment() == null) {
        final TabFragmentInflater tabInf = new TabFragmentInflater();
        newTab.setFragment(Fragment.instantiate(this, tabInf.getClass().getName(), newTab.getArgs()));
        ft.add(R.id.realtabcontent, newTab.getFragment(), newTab.getTag());
      }
      else
        ft.attach(newTab.getFragment());
    }

    ft.commit();
    this.getSupportFragmentManager().executePendingTransactions();

    lastTab = newTab;
  }
}

So the question is the following:

Why onAttach()/onDetach() callbacks are not fired in the LoginRowFragment class when using .attach() or .detach() on them, but get fired if I use .add() or .remove() respectively?

nKn
  • 13,691
  • 9
  • 45
  • 62
  • See http://stackoverflow.com/questions/9156406/whats-the-difference-between-detaching-a-fragment-and-removing-it – Ryan Byrne Mar 14 '14 at 15:05
  • I know what's the difference, the question is why `.remove()` calls the `onDetach()` callback and `.detach()` doesn't in my case. – nKn Mar 14 '14 at 15:07
  • By logic it could be because state of the fragment with `.detach()` is managed by fragment manager (you don't care, manager will do everything), with `.remove` you lose everything and `onDetach` could be used to save state. (`onDetach` is called after `onDestroy`) – Marco Acierno Mar 16 '14 at 15:16
  • As I understand it, on both it should be fired `onDetach()`, independently if the state is saved or not. It's like the login bar `Fragment` wouldn't be detached because is held by something, but in the case this is the reason, I can't find out why. – nKn Mar 16 '14 at 15:23
  • Try not declaring your Fragment (login) in XML… instead create a proper transaction using the same FragmentManager. – Martin Marconcini Mar 16 '14 at 15:37
  • do you use a animation on your Fragment's logic? – Sergey Shustikov Mar 23 '14 at 13:02
  • @deathember No animation there – nKn Mar 23 '14 at 14:05

1 Answers1

4

You cannot remove an instance of a Fragment which has been declared in your layout XML file (by design)

When you use FragmentTransactions, you're manipulating ViewGroups containing the layout of a Fragment.

When you declare your Fragment/Class in a layout, it's part of the View hierarchy, so you can't remove it.

You can, however, add a New Fragment into that layout, because it will act as a container.

Try changing your Layout from:

    <fragment 
      android:id="@+id/firsttab_loginrow"
      class="com.mydomain.myproject.LoginRowFragment"
      android:tag="1"
      android:layout_width="match_parent"
      android:layout_height="wrap_content" />

to:

    <FrameLayout 
      android:id="@+id/firsttab_loginrow"
      android:tag="1"
      android:layout_width="match_parent"
      android:layout_height="wrap_content" />

And create a FragmentTransaction to add/replace/show/hide according to what you want to do.

Martin Marconcini
  • 26,875
  • 19
  • 106
  • 144
  • Thanks for your answer. I just did what you proposed, I changed the `fragment` to a `FrameLayout` and used `FragmentTransaction` to put the `fragment` inside via `.add()`: `final FragmentTransaction fth = getActivity().getSupportFragmentManager().beginTransaction();`, `fth.add(R.id.firsttab_loginrow, new LoginRowFragment());`, `fth.commit();`. It renders the layout correctly, I can swap between tabs but same result, if I change `.remove()` to `.detach()` it still doesn't fire `onDetach()`. Weird... – nKn Mar 16 '14 at 20:07
  • Hmmm I think that the Tab Adapter may not be detaching it… (thinking) – Martin Marconcini Mar 16 '14 at 20:21
  • I think that if the Activity is not stopped, it may be tied through the TabHost and therefore is not detached… – Martin Marconcini Mar 16 '14 at 20:26
  • I'm of the same opinion, there must be something tying the `Activity`, but the question is why `.remove()` indeed removes the `Fragment` but `.detach()` doesn't? In my opinion it should do the same on both... – nKn Mar 16 '14 at 21:08
  • 1
    From documentation, it says that onAttach(Activity) is called once the fragment is associated with its activity and onDetach() is called immediately prior to the fragment no longer being associated with its activity. When you call detach(), the fragment is still being actively managed by the fragment manager which is not the case with remove. We use attach() only when the fragment is previously detached using detach() unlike add(). – Madala Mar 23 '14 at 11:02
  • @Madala is correct. `onDetach` and `detach` are not referring to the same thing. Their names were poorly chosen. – mhsmith Aug 28 '18 at 17:13