Activity And Fragment Interaction

2019-01-18 22:48发布

I have an Activity with multiple Fragments. I want to show a DialogFragment or open another Fragment from one of the Fragments. I know that an Activity should be the one tasked with opening Fragments so instead I have tried a couple things.

FIRST
I tried to use getActivity() and cast it so I can call a method in the Activity to show a Fragment however this creates a dependency in the Fragment with the Activity and I would like to avoid adding a dependency if possible.

SECOND
Next I tried a listener to notify the Activity that it should show a Fragment. So I created a class in the Activity to implement the listener interface. But I had problems because I had to use New MyActivity().new Listener(); and it would throw an Exception when I tried to use getSupportFragmentManager() since this instance of the Activity is not initialized.

THIRD
I then tried to have the Activity implement the listener directly which works because then I am only creating a dependency with the listener and not the Activity. However now I am getting to the point where my Activity will be implementing 2 - 4 different interfaces which is making me hesitant because it will severely reduce cohesion.

So any way I have tried I seem to be running into a brick wall and creating dependancies I'm not sure I need to be creating. Am I screwed and have to go with one of these options? If so which option would be best? Any help or suggestions are are greatly appreciated.

7条回答
Deceive 欺骗
2楼-- · 2019-01-18 23:27

You will need to pass your data from Fragment X up to your FragmentActivity which will pass this data on to your Fragment Y. You do that by way of an interface defined in your fragment class and instantiate a callback that is defined in onAttach().

More information on how to do this here Communication With other Fragments

Quick example, consider Fragment A and Fragment B. Fragment A is a list fragment and whenever an item is selected it will change what is displayed in Fragment B. Simple enough, right?

At first, define Fragment A as such.

 public class FragmentA extends ListFragment{

   //onCreateView blah blah blah

}

And here's Fragment B

public class FragmentB extends Fragment{

 //onCreateView blah blah blah

}

And here's my FragmentActivity that will govern them both

public class MainActivity extends FragmentActivity{

//onCreate 
//set up your fragments

}

Presumably you have something like this already, now here's how you would change FragmentA(the list fragment that we need to get some data from).

    public class FragmentA extends ListFragment implements onListItemSelectedListener, onItemClickListener{

OnListItemSelectedListener mListener;

   //onCreateView blah blah blah



 // Container Activity must implement this interface
    public interface OnListItemSelectedListener {
    public void onListItemSelected(int position);
}


}


  @Override
  public void onAttach(Activity activity) {
    super.onAttach(activity);

    // This makes sure that the container activity has implemented
    // the callback interface. If not, it throws an exception
    try {
        mListener = (OnListItemSelectedListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement OnListItemSelectedListener");
    }
}


  @Override 
 public void onItemClick(AdapterView<?> parent, View view, int position, long id){


 //Here's where you would get the position of the item selected in the list, and  pass    that position(and whatever other data you need to) up to the activity 
//by way of the interface you defined in onAttach

  mListener.onListItemSelected(position);


}

The most important consideration here is that your parent Activity implements this interface, or else you will get an exception. If implemented successfully, everytime an item in your list fragment is selected, your Activity will be notified of it's position. Obviously you could alter your interface with any number or type of parameters, in this example we're just passing in our integer position. Hope this clarifies a bit man, good luck.

查看更多
疯言疯语
3楼-- · 2019-01-18 23:28

To get a maximum in loose coupling you can use an event bus like OTTO from Square or EventBus from GreenRobot. Your fragments can fire events which are handled by your activity and vice versa. The cool thing about this is that the components (activities, fragments) no nothing about each other and you do not need to declare any interfaces or callbacks.

I use it in all my projects and it is robust and has low to no influence on the performance (in normal conditions).

查看更多
萌系小妹纸
4楼-- · 2019-01-18 23:29

Personally I would say that fragments should be thought as reusable and modular components. So in order to provide this re-usability, fragments shouldn't know much about their parent activities. But in return activities must know about fragments they are holding.

So, the first option should never be considered in my opinion for the dependency reason you mentioned causing a very highly coupled code.

About the second option, fragments may delegate any application flow or UI related decisions (showing a new fragment, deciding what to do when a fragment specific event is triggered etc..) to their parent activities. So your listeners/callbacks should be fragment specific and thus they should be declared in fragments. And the activities holding these fragments should implement these interfaces and decide what to do.

So for me the third option makes more sense. I believe that activities are more readable in terms of what they are holding and doing on specific callbacks. But yes you are right your activity might become a god object.

Maybe you can check Square's Otto project if you don't want to implement several interfaces. It's basically an event bus.

查看更多
Juvenile、少年°
5楼-- · 2019-01-18 23:30

Just follow the documentation:

Fragment:

public class HeadlinesFragment extends Fragment {

    // Container Activity must implement this interface
    public interface OnHeadlineSelectedListener {
        public void onArticleSelected(int position);
    }

    OnHeadlineSelectedListener mCallback;

    // "Bind" to the Activity where this Fragment lives
    public void setOnHeadlineSelectedListener(Activity activity) {
        mCallback = (OnHeadlineSelectedListener) activity;
    }

    // ...

    // This will send info to the Activity where the Fragment lives
    private void someOtherFunctionOrListener(int position) {
        mCallback.onArticleSelected(position); // pass info into Activity
    }
}

Activity:

public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    // ...

    @Override
    public void onAttachFragment(Fragment fragment) {
        if (fragment instanceof HeadlinesFragment) {
            HeadlinesFragment headlinesFragment = (HeadlinesFragment) fragment;
            headlinesFragment.setOnHeadlineSelectedListener(this);
        }
    }

    @Override
    public void onArticleSelected(int position) {
        // Call received from Fragment
    }
}
查看更多
趁早两清
6楼-- · 2019-01-18 23:31

I think your second option is on the right track.

In your fragment, define the listener interface:

class MyFragment ...
{
    public interface IMyFragmentListenerInterface
    {
        void DoSomething();
    }
}

Have the activity implement the interface:

class MyActivity
{
    class MyListener1 implements IMyFragmentListenerInterface { ... }
}

Pass the listener to the fragment. I like to do it in the Fragment's constructor, but that only works if you manage your fragments entirely by yourself. You can add a setListener method to your fragment instead.

查看更多
forever°为你锁心
7楼-- · 2019-01-18 23:37

Have you tried something like this (from the Fragment):

FragmentTransaction ft = 
    getActivity().getSupportFragmentManager().beginTransaction();
Fragment prev = 
    getActivity().getSupportFragmentManager().findFragmentByTag("some_name");
if (prev != null) {
    ft.remove(prev);
}
ft.addToBackStack(null);

DialogFragment dialogFragment = DialogFragmentClass.newInstance();
dialogFragment.show(ft, "some_name");

Let me know, cheers.

查看更多
登录 后发表回答