androidmvpandroid-mvp

How to Access the Fragment Manager (or the Hosting Activity) from the Presenter


I need to access the fragment manager (or activity) from my presenter. How can I access it? The presenter itself is being called from another presenter which in turn is called from a fragment. How can I access the fragment manager or the hosting activity from the presenter? Do I need to implement an interface? Thanks in advance.

public class EPGTitleRowPresenter extends Presenter {

private final static String TAG = EPGTitleRowPresenter.class.getSimpleName();
private Context mContext;

private static int sSelectedBackgroundColor;
private static int sDefaultBackgroundColor;

public EPGTitleRowPresenter(Context context) {
    mContext = context;

}

private View.OnFocusChangeListener mOnFocusChangeListener = new View.OnFocusChangeListener() {
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if (hasFocus) {
            updateEPGTitleRowBackgroundColor(v, true);
        }
        else {
            updateEPGTitleRowBackgroundColor(v, false);
        }
    }
};

private View.OnClickListener mOnClickListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        VerticalGridView vgv = (VerticalGridView) v.getParent();
        int pos = vgv.getSelectedPosition();

        if (pos > 0) {
            //TODO: mark programme for auto-switch
        }
        else {
            //TODO: switch to channel

        }
    }
};

private static void updateEPGTitleRowBackgroundColor(View view, boolean selected) {
    int color = selected ? sSelectedBackgroundColor : sDefaultBackgroundColor;
    // Both background colors should be set because the view's background is temporarily visible
    // during animations.
    view.setBackgroundColor(color);
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
    EPGTitleRowView view = new EPGTitleRowView(parent.getContext()) {
        @Override
        public void setSelected(boolean selected) {
            Log.i(TAG, "setSelected called");
            super.setSelected(selected);
        }
    };
    view.setMinimumWidth(parent.getWidth());

    sDefaultBackgroundColor =
            ContextCompat.getColor(parent.getContext(), android.R.color.transparent);

    sSelectedBackgroundColor =
            Color.parseColor("#66ffe680");

    view.setFocusable(true);
    view.setFocusableInTouchMode(true);
    updateEPGTitleRowBackgroundColor(view, false);

    view.setOnClickListener(mOnClickListener);
    view.setOnFocusChangeListener(mOnFocusChangeListener);

    return new ViewHolder(view);
}

@CallSuper
@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
    final Programme prog = (Programme) item;
    ViewHolder vh = (ViewHolder) viewHolder;

    vh.mProgrammeTitle.setText(prog.getTitle().get(0));

    Date startDateTime = new Date(prog.getStart_utc() * 1000l);
    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
    String strStartTime = sdf.format(startDateTime);
    vh.mProgrammeStartTime.setText(strStartTime);
}


public class ViewHolder extends Presenter.ViewHolder {
    public EPGTitleRowView mRootView;
    public TextView mProgrammeTitle;
    public TextView mProgrammeStartTime;

    public ViewHolder(View view) {
        super(view);
        mRootView = (EPGTitleRowView) view;
        mProgrammeTitle = (TextView) view.findViewById(R.id.programme_title);
        mProgrammeStartTime = (TextView) view.findViewById(R.id.programme_start_time);
    }
}


public class EPGTitleRowView extends FrameLayout {

    public EPGTitleRowView(Context context) {
        super(context);

        setFocusable(true);
        setFocusableInTouchMode(true);

        LayoutInflater inflater = LayoutInflater.from(getContext());
        inflater.inflate(R.layout.epg_title_row, this);
    }
}
}

Solution

  • If you are using a presenter I assume you are working with a MVP pattern. If that the case it's better to keep your code as far away from Android components (like the fragmentManager) as possible, for a better decouple.

    My suggestion would be to call a method in the View (assuming the view is an Activity), and the View would needs to be known to the presenter, thus you could use and interface for the view (which you are probably using)

    Something like this:

    public interface MyView {
        void methodToUseFragmentManager();
    }
    

    The presenter needs to be aware of it, so it could looks like this:

    MyView view:
    public EPGTitleRowPresenter (MyView view){
        this.view = view;
    }
    
    void methodToUpdateView(){
         view.methodToUseFragmentManager();
    }
    

    And finally implement that interface on your Activity:

    public class MyActivity extends AppCompatActivity implements MyView{
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            presenter = new EPGTitleRowPresenter(this);
        }
    
        @Override
        void methodToUseFragmentManager(){
            // Here you can access the FragmentManager
        }
    }
    

    I've also notice that your presenter gets the Context, to have things decoupled as said above that won't be a good idea. Whenever you need to access the context call a method in the View

    EDIT

    If you have more than 1 presenter, let's say you have 2: the parentPresenter and childPresenter, being EPGTitleRowPresenter the child

    So your parent present should look like:

    MyView view:
    EPGTitleRowPresenter childPresenter;
    public ParentPresenter (MyView view){
        this.view = view;
        childPresenter = new EPGTitleRowPresenter(view);
    }
    

    and the child (EPGTitleRowPresenter) would be the same as in the original answer.

    Your activity will look like:

    public class MyActivity extends AppCompatActivity implements MyView{
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            presenter = new ParentPresenter(this);
        }
    
        @Override
        void methodToUseFragmentManager(){
            // Here you can access the FragmentManager
        }
    }