Android MVP - How to communicate between activity presenter and fragment presenter
Asked Answered
P

6

27

I have an activity with 3 fragments, currently I use ViewPager. I want to implement MVP and communicate between activity presenter and fragment presenters i.e:

  • Passing data from activity presenter to fragment presenters
  • Sending event from fragment presenters to activity presenter
  • ...

But I don't know how to do it in official way. I can use BusEvent but I don't think it's a good practice.

Phrixus answered 19/4, 2016 at 3:0 Comment(3)
Can you provide more details about your use case? because there are some possible answers in different use cases like: creating a universal business rule, using dependency injection, using event bus, passing bundle to your fragments, etc.Eolanda
Can you mention examples of data/events that you want to pass between presenters? Do you they a common data source for instance?Clinandrium
This answers nearly the same question: https://mcmap.net/q/535256/-communicate-between-presenters-in-mvp-android-applicationChromatics
W
5

Communication between fragments and activity or vice-versa can be done by using nnn's answer or you could use ViewModel and LiveData witch provides a cleaner way and respect the lifecycle from fragments and activities which can save from writing a few lines of code in attempt to prevent a a non-visible fragment from receiving data on the background.

First you extend the ViewModel class, initialize the Livedata and some helper methods.

public class MyViewModel extends ViewModel {
private MutableLiveData<String> toFragmentA, toFragmentB;
private MutableLiveData<List<String>>  toAllFragments;

public MyViewModel() {
    toFragmentA = new MutableLiveData<>();
    toFragmentB = new MutableLiveData<>();
    toAllFragments = new MutableLiveData<>();
}

public void changeFragmentAData(String value){
    toFragmentA.postValue(value);
}
public void changeFragmentBData(String value){
    toFragmentB.postValue(value);
}
public void changeFragmentAllData(List<String> value){
    toAllFragments.postValue(value);
}

public LiveData<String> getToFragmentA() {
    return toFragmentA;
}

public LiveData<List<String>> getToAllFragments() {
    return toAllFragments;
}

public LiveData<String> getToFragmentB() {
    return toFragmentB;
}

}

Then you initialize the ViewModel on your activity.

public class MainActivity extends AppCompatActivity {
private ViewPager viewPager;
private TabLayout tabLayout;
MyViewModel mViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mViewModel = ViewModelProviders.of(this)
            .get(MyViewModel.class);

    viewPager.setAdapter(new Adapter(getSupportFragmentManager()));


}

}

reading the data in the fragments:

 @Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mViewModel = ViewModelProviders.of(getActivity()).get(MyViewModel.class);

    mViewModel.getToAllFragments().observe(this, new Observer<List<String>>() {
        @Override
        public void onChanged(List<String> s) {
            myList.addAll(s);
            //do something like update a RecyclerView
        }
    });

    mViewModel.getToFragmentA().observe(this, new Observer<String>() {


        @Override
        public void onChanged(String s) {
            mytext = s;
            //do something like update a TextView
        }
    });
}

to change the values of any of the live datas you can use one of the methods in any of the fragments or in the activity:

changeFragmentAData();
changeFragmentBData();
changeFragmentAllData();

Whats happing behind the scenes:

when you use mViewModel = ViewModelProviders.of(this).get(MyViewModel.class) you are creating a n instance of ViewModel and binding it to the lifecycle of the given activity of fragment so the view model is destroid only the the activity or fragement is stopped. if you use mViewModel = ViewModelProviders.of(getActivity()).get(MyViewModel.class)you are bindig it to the lifecycle if the parentactivity`

when you use mViewModel.getToFragmentA().observe() or mViewModel.getToFragmentB().observe() or mViewModel.getToAllFragments().observe() you are connecting the LiveData in MyViewModel class to the given fragment or activity an the value of the onChange() method is updated in all the classes that are observing the method.

I recomend for personal expirience a bit of research about Livedata end ViewModel which ou can on youtube or this link

Willwilla answered 7/4, 2019 at 14:29 Comment(0)
T
2

As per my understanding, for your UseCase, suppose ActivityA have a viewPager having 3 Fragments(FragmentA, FragmentB, FragmentC).

ActivityA have ActivityPresenterA

FragmentA have FragmentPresenterA

As per MVP, FragmentPresenterA should be responsible for all the logical and business flows of FragmentA only and should communicate with FragmentA only. Therefore, FragmentPresenterA can not directly communicate with ActivityPresenterA.

For communication from Fragment to Activity, presenter should not be involved and this should be done as we would communicate in non-MVP architecture, i.e. with the help of interface.

Same applies for Activity to Fragment communication.

For communication between Activity and Fragment read here

Tyronetyrosinase answered 10/2, 2017 at 14:1 Comment(4)
Is this approach the cleanest? What if these fragments are related only to data? Should not this be done differently in this case?Jeannajeanne
If all the fragments of the ViewPager are related to each other with data or shares common data, in that case Fragments need not to have different presenters rather ActivityPresenter should handle all the business logic of these fragments and pass on the data to corresponding fragments with the help of interfaces.Tyronetyrosinase
I like the idea simply because I have the same opinion. Now imagine nested fragments or fragments in viewpager each having totally unrelated functionality and with quite a handful of operations in each. How feasible is it to combine all the unrelated business logic parts into one gigantic presenter?Bookbinding
@Farid: In such condition, you can have separate presenter for each nested fragment to do the business logic for that nested fragment. And the fragment transactions for these nested fragments should be handled by the presenter of the parent fragment.Tyronetyrosinase
J
1

You can use one presenter for that case.

Used your Activity Presenter to get all the data that your fragments need. then create an interface class and implement it to your fragments.

For example:

Create a public interface for your PageAFragment (this interface will the bridge of your data from activity to fragment). and use the method of your interface to handle the result from your presenter to view.

This is the example of interface class that I created for received data. for the parameter you can choose what you want it depends on your need, but for me I choose model.

public interface CallbackReceivedData {
    void onDataReceived(YourModel model);
}

In MainActivity Class check the instance of fragment that attached into your activity. put your checking instance after you commit the fragment.

public class MainActivity extends AppCompatActivity{

  private CallbackReceivedData callbackReceivedData;

  @Override
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      //after commit the fragment
    if (fragment instanceof PageAFragment){
            callbackReceivedData = (CallbackReceivedData)fragment;
    }

  }
  //this is the example method of MainActivity Presenter, 
  //Imagine it, as your view method.
  public void receivedDataFromPresenter(YourModel model){
      callbackReceivedData.onDataReceived(model);
  }

}

I assumed that the receivedDataFromPresenter is the received method of our view and get data to presenter.

And now we will pass the data from presenter to callbackReceivedData

In PageAFragment implement the CallbackReceivedData and Override the onDataReceived method. Now you can passed the data from activity to your fragment.

public class PageAFragment extends Fragment implements CallbackReceivedData{

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    public void onDataReceived(YourModel model) {

    }
}

Note: Alternative way, you can use Bundle and pass the data with the use of setArguments.

If you want to send Event from Fragment to Activity you can follow this Idea.

Create an Interface class and implement it to your MainActivity and Override the method from interface to your activity, for My case I do it something like this.

Here's my CallbackSendData Class.

public interface CallbackSendData {
    void sendDataEvent(String event);
}

Implement CallbackSendData interface to your MainActivity and Override the sendDataEvent method.

public class MainActivity extends AppCompatActivity implements CallbackSendData{

  private CallbackReceivedData callbackReceivedData;

  @Override
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

     //after commit the fragment
     if (fragment instanceof PageAFragment){
         callbackReceivedData = (CallbackReceivedData)fragment;
     }

  }

  //this is the example method of MainActivity Presenter, 
  //Imagine it, as your view method.
  public void receivedDataFromPresenter(YourModel model){
      callbackReceivedData.onDataReceived(model);
  }    


  @Override
  public void sendDataEvent(String event){
     //You can now send the data to your presenter here.
  }



}

And to your PageAFragment you need to use attach method to cast your interface. The attach method called once the fragment is associated with its activity. If you want to understand the lifecycle of fragment just click this link: https://developer.android.com/reference/android/app/Fragment.html.

public class PageAFragment extends Fragment implements CallbackReceivedData{

    private CallbackSendData callbackSendData;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


    }

    @Override
    public void onDataReceived(YourModel model) {
        //Received the data from Activity to Fragment here.
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup 
    container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.PagerAFragment, container, 
    false);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle 
    savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Button Eventbutton;

        Eventbutton = view.findViewById(R.id.event_button);
        Eventbutton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                callbackSendData.sendDataEvent("send Data sample");
            }
        });
    }

   @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try{
            callbackSendData = (CallbackSendData) context;
        }catch (ClassCastException e){
            e.printStackTrace();
        }
    }

}

And now you can use the CallbackSendData to send the data from activity to fragment.

Note: It's much easier if you are using Dependency Injection to your project, you can use Dagger2 library.

Goodluck.

Jarv answered 4/3, 2018 at 13:13 Comment(0)
C
0

To communicate between a Fragment and an Activity (whether between their presenters or their classes), you need an interface that your activity implements (like ShoppingInteractor).

This way you can call ((ShoppingInteractor)getActivity()).doSomething() in the fragments. If you want your activity's presenter to handle the task, you need to call the presenter in the doSomething inside the activity.

You can do the same with the fragments with another interface and call the fragment's interactor inside the activity.

You can even have a Presenter getPresenter() inside these interfaces to have access to the actual presenter. (((ShoppingInteractor)getActivity()).getPresenter().sendData(data)). Same goes for the fragments.

Cornel answered 20/3, 2018 at 15:14 Comment(0)
H
0

Dynamic data:

Here is an example using rxjava2, dagger2 and moxy.

Conditionalities:

  • Presenters do not depend on the life cycle of the view
  • One presenter - one view. The views do not share the presenters among themselves and one view has only one presenter.

The solution is similar to the EventBus, but instead uses Subject with a limited lifetime. It is in the component that is created when the activity starts and is destroyed when it exits. Both activity and fragments have an implicit access to it, they can change the value and respond to it in their own way.

Example project: https://github.com/Anrimian/ViewPagerMvpExample

Static data:

Just use arguments in the fragment and that's it.

Hydrosome answered 28/5, 2018 at 14:33 Comment(2)
How we can collect one by one information from all fragments in one model?Corpulent
I didn't understand exactly what you mean, but in common Interactor you can collect all information and observe changes on it.Hydrosome
A
0

If you want to use MVP, the first step is to create one presenter for each View, I mean, If you have 3 fragments, then would have 3 presenters. I think that is a bad idea to create one presenter for 4 views (activity and 3 fragments).

Ashwell answered 4/4, 2019 at 11:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.