7

I can easily detect when Fragments are attached to Activity via Activity.onAttachFragment()

But how can I detect in Activity that some Fragment is detached from activity? There is no Activity.onDetachFragment()

Is subcclasing Fragment and write some code to notify Activity about that state is the only solution?

Dominik Suszczewicz
  • 1,469
  • 2
  • 16
  • 23

5 Answers5

11

you can use interface for communicating between Fragment and Activity

something like this :

  public Class MyFragment extends Fragment {
    FragmentCommunicator communicator;
    public void setCommunicator(FragmentCommunicator communicator) {
      this.communicator = communicator;
    }
    @Override
    public void OnDetach() {
      communicator.fragmentDetached();
    }
    ...

     public Interface FragmentCommunicator {
        public void fragmentDetached();
     }
  }

and in your activity :

 public Class MyActivity extends Activity Implements FragmentCommunicator {
   ...
   MyFragment fragment = new MyFragment();
   fragment.setCommunicator(this);
   ...
   @Override 
   public void fragmentDetached() {
       //Do what you want!
   }
 }

Edit:

the new approach is setting interface instance in onAttach.

public void onAttach(Activity activity) {
    if (activity instanceof FragmentCommunicator) {
        communicator = activity;
    } else {
        throw new RuntimeException("activity must implement FragmentCommunicator");
    }
}

now there is no need to have setCommunicator method.

Mohammad Rahchamani
  • 5,002
  • 1
  • 26
  • 36
  • I still support the original method you outlined here. `Fragment.onAttach()` breaks inversion of control since the fragment is making assumptions [dictations] about the surrounding system - especially when it crashes the system when the system isn't designed the way it wants. – methodsignature Mar 15 '18 at 14:25
  • Another note. I would, however, internalized the fragment communicator as a member of the activity. I'll go ahead and write up a new answer putting that together. – methodsignature Mar 15 '18 at 14:34
2

Mohammad's original answer is close to I would do. He has since updated it to leverage a mechanism provided by Android - Fragment.onAttach(Context context).In that approach, the fragment grabs components (ie, the activity) from the system and calls into it. This breaks inversion of control.

Here is my preferred approach:

public class MyActivity extends AppCompatActivity {

    @Override
    public void onAttachFragment(Fragment fragment) {
        super.onAttachFragment(fragment);
        if (fragment instanceof MyFragment) {
            ((MyFragment) fragment).setListener(mMyFragmentListener);
        }
    }

    private final MyFragment.Listener mMyFragmentListener = new MyFragment.Listener() {
        @Override
        public void onDetached(MyFragment fragment) {
            fragment.setListener(null);
        }

        // implement other worker methods.
    };
}

public class MyFragment extends Fragment {

    @Nullable
    private Listener mListener;

    public void setListener(@Nullable Listener listener) {
        mListener = listener;
    }

    public interface Listener {
        void onDetached(MyFragment fragment);

        // declare more worker methods here that leverage the connection.
    }

    @Override
    public void onDetach() {
        super.onDetach();

        if (mListener != null) {
            mListener.onDetached(this);
        }
    }
}

In this solution, the fragment doesn't dictate it's surroundings. Some control is given the to fragment in that it breaks the connection itself. We also already don't own the detaching of the fragment anyways, so clearing the listener is really just cleanup.

Here is an alternative approach that is more explicit, less prone to developer error, but also creates extra boiler plate (I prefer the previous approach because the goodbye handshake feels like an unnecessary distraction):

public static class MyActivity extends AppCompatActivity {

    @Override
    public void onAttachFragment(Fragment fragment) {
        super.onAttachFragment(fragment);
        if (fragment instanceof MyFragment) {
            ((MyFragment) fragment).setListener(mMyFragmentListener);
        }
    }

    private final MyFragment.Listener mMyFragmentListener = new MyFragment.Listener() {
        @Override
        public void onDetached(MyFragment fragment) {
            fragment.setListener(null);
        }

        // implement other worker methods.
    };
}

public static class MyFragment extends Fragment {

    @Nullable
    private Listener mListener;

    public void setListener(@Nullable Listener listener) {
        mListener = listener;
    }

    public interface Listener {
        void onDetached(MyFragment fragment);

        // declare more worker methods here that leverage the connection.
    }

    @Override
    public void onDetach() {
        super.onDetach();

        if (mListener != null) {
            mListener.onDetached(this);
        }
    }
}
methodsignature
  • 4,152
  • 2
  • 20
  • 21
1

You have a callback in the fragment life cycle. onDetach() is called when fragment is no longer attached to activity.

N Sharma
  • 33,489
  • 95
  • 256
  • 444
  • 2
    In that this is a solution, it's probably is a valid answer to "is the only solution?". Inversion of control sadness. The fragment now controls the system (ie fragment controls the activity), instead of the system controlling it. Not sure why Google hasn't provided support for better engineering on this issue within the support library. – methodsignature Mar 15 '18 at 13:55
1

An alternative would be:

mFragmentManager.findFragmentByTag("Tag").getView()

If the view is null the fragment must be detached.

KaRa
  • 59
  • 7
0

You can use ViewModel for update host activity. Shared ViewModel could be better choice than across the old listener based polymorphism model. You can follow the official documentation.

Data, fragment lifecyle etc. can be observable with shared viewmodel.

sealed class FragmentStates {
    object Attached : FragmentStates()
    object Started : FragmentStates()
    object Stopped : FragmentStates()
    object DeAtached : FragmentStates()
}

class FragmentStateViewModel : ViewModel() {
    private val _fragmentState = MutableLiveData<FragmentStates>()
    val fragmentStates: LiveData<FragmentStates> get() = _fragmentState

    fun fragmentAttached() {
        _fragmentState.value = FragmentStates.Attached
    }

    fun fragmentDeAtached() {
        _fragmentState.value = FragmentStates.DeAtached
    }

}

class HostActivity : AppCompatActivity() {
    private val fragmentStateViewModel: FragmentStateViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        fragmentStateViewModel.fragmentStates.observe(this, Observer {
          when(it) {
              FragmentStates.Attached -> {}
              FragmentStates.Started -> {}
              FragmentStates.Stopped -> {}
              FragmentStates.DeAtached -> {}
          }
        })
    }
}

class MyFragment: Fragment() {
    private val fragmentStateViewModel: FragmentStateViewModel by activityViewModels()

    override fun onAttach(context: Context) {
        super.onAttach(context)
        fragmentStateViewModel.fragmentAttached()
    }

    override fun onDetach() {
        super.onDetach()
        fragmentStateViewModel.fragmentDeAtached()
    }
}
Cagdas
  • 101
  • 2
  • 9