简体   繁体   中英

Is my implementation pattern for MVP valid?

I'm new to Android and MVP in-general, and I've been doing iOS programming for the last 1.5 years, so I find delegate patterns easy to digest. I've implemented MVP in such a way that the view conforms to a presenter's protocol, which lets the presenter disregard the view's specific type, but lets it know that certain methods are a given and thus okay to call on the "view." I've been reading various MVP guides, and all of the Mosby tutorials, and I'm not sure I agree with some of it. Is the pattern I've implemented kosher? I'd like some feedback so I don't keep heading in a bad direction, if that is indeed what I'm doing...

For example,

Base Presenter:

public abstract class Presenter<V, S> implements BasePresenterInterface<V, S> {

    public interface PresenterProtocol extends BasePresenterProtocol {

    }

    private WeakReference<V> mAttachedView = null;
    private S mService = null;

    /**
     * Interface Overrides
     */
    @Override
    public void attachView(V view) {
        boolean viewDoesNotConform = !viewDoesConform(view);
        if (viewDoesNotConform) {
            Log.d("DEBUG", "Cannot attach View that does not conform to PresenterProtocol");
            return;
        }

        mAttachedView = new WeakReference<>(view);
        ((BasePresenterProtocol) getAttachedView()).onViewAttached();
    }

    @Override
    public void detachView() {
        mAttachedView = null;
    }

    @Override
    public boolean viewDoesConform(V view) {
        Class<?> klass    = view.getClass();
        boolean  conforms = BasePresenterInterface.BasePresenterProtocol.class.isAssignableFrom(klass);

        return conforms;
    }

    @Override
    public boolean viewIsAttached() {
        return mAttachedView != null;
    }

    @Override
    public V getAttachedView() {
        return mAttachedView.get();
    }

    @Override
    public S getService() {
        return mService;
    }

    @Override
    public void setService(S service) {
        mService = service;
    }
}

I then subclass this into the following:

PhotoRecyclerPresenter:

public class PhotoRecyclerPresenter extends Presenter<PhotoRecyclerPresenter.PhotoRecyclerPresenterProtocol, PhotoService> {
    public interface PhotoRecyclerPresenterProtocol extends Presenter.PresenterProtocol {
        void onPhotosLoaded(List<TestPhoto> photoList);
        void onItemSelected(TestPhoto photo);
        void onShowDetail(TestPhoto photo);
    }

    private static PhotoRecyclerPresenter mSharedInstance;

    private PhotoRecyclerPresenter() {
        setService(new PhotoService());
    }

    /**
     * External Methods
     */

    public void getPhotos() {
        boolean noAttachedView = !viewIsAttached();
        if (noAttachedView) {
            Log.d("DEBUG", "No view attached");
            return;
        }

        getService().getAPI()
                    .getPhotos()
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(photoList -> getAttachedView().onPhotosLoaded(photoList));
    }


    /**
     * Internal Methods
     */

    public static PhotoRecyclerPresenter getSharedInstance() {
        boolean firstInstance = mSharedInstance == null;
        if (firstInstance) {
            setSharedInstance(new PhotoRecyclerPresenter());
        }

        return mSharedInstance;
    }

    public static void setSharedInstance(PhotoRecyclerPresenter instance) {
        mSharedInstance = instance;
    }

    public void didSelectItem(TestPhoto photo) {
        getAttachedView().showDetail(photo);
    }
}

And it communicates with the view: PhotoRecyclerFragment:

public class PhotoRecyclerFragment extends Fragment implements PhotoRecyclerPresenter.PhotoRecyclerPresenterProtocol {

    private RecyclerView               mRecyclerView;
    private RecyclerView.LayoutManager mLayoutManager;
    private Activity                   mParentActivity;
    private PhotoRecyclerPresenter     mPresenter;
    private PhotoRecyclerAdapter mAdapter;

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

    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_recycler, container, false);

        mParentActivity = getActivity();

        mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerView);
        mLayoutManager = new LinearLayoutManager(mParentActivity);
        mAdapter = new PhotoRecyclerAdapter(this);

        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setAdapter(mAdapter);

        mPresenter = PhotoRecyclerPresenter.getSharedInstance();
        mPresenter.attachView(this);

        return rootView;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mPresenter.detachView();
        mAdapter.clear();
    }


    /**
     * PhotoRecyclerPresenterProtocol Methods
     */


    @Override
    public void onItemSelected(TestPhoto photo) {
        mPresenter.didSelectItem(photo);
    }

    @Override
    public void onPhotosLoaded(List<TestPhoto> photoList) {
        mAdapter.loadPhotos(photoList);
    }

    @Override
    public void onViewAttached() {
        mPresenter.getPhotos();
    }

    @Override
    public void onViewDetached() {

    }

    @Override
    public void onShowDetail(TestPhoto photo) {
        Intent detailIntent = new Intent(mParentActivity, PhotoDetailActivity.class);
        mParentActivity.startActivity(detailIntent.putExtra(Intent.EXTRA_UID, photo.getPhotoId()));
    }
}

This lets me define a set of requirements a view needs to conform to in order to utilize the singleton presenter, while keeping the presenter agnostic about what views use it, as long as they conform to its protocol. So far in my practice project it seems to work fine, but I can't seem to find any resources where what I'm doing is recommended as far as MVP goes, and I have enough self-doubt that I figured I'd ask my first StackOverflow question. Can anyone who has experience with MVP shed some light on this?

Also, if I'm asking in the wrong place, feel free to point me to the correct place to post this.

Thanks :)

From my point of view you are doing the same thing that Mosby does. The only difference is the name of the interface (or protocol in objective-c) world. You call it PresenterProtocol while Mosby call it MvpView . Both are doing the same job: Offering the Presenter an Api of methods the presenter can call to manipulate the view.

The only thing that doesn't make sense is to have a method viewDoesConform() . In Java you have type safety. You can use the generics type V of your Presenter to ensure that your fragment is implementing the Presenter's protocol. just change it to V extends BasePresentersProtocol

Furthermore I think that it doesn't make sense to have a "shared instance" (aka Singleton pattern) of the presenter. I think it would make more sense to have a "shared instance" of the PhotoService. But But please note also that by doing so your code is not testable (unit tests) anymore. You should google for Dependency injection or Inverse of Control to understand how to write modular, reusable and testable code. I'm not talking about dependency injection frameworks like Dagger , spring or guice. You just should understand the idea behind dependency injection. You can write classes following this principle completely without dependency injection frameworks (ie using constructor parameters).

Side note: you never unsubscribe your presenter from PhotoService. Depending on how PhotoService is implemented you may have a memory leak because PhotoService observable has a reference to the presenter which prevents the presenter and PhotoService (depending on your concrete implementation) from being garbage collected.

Edit: Mosby defines the protocol for the View. Have a look at the getting started section on the project website. The HelloWorldView defines two methods: showHello() and showGoodbye() (implented by the HelloWorldActivity ) and HelloWorldPresenter calls these two methods to manipulate the View. The HelloWorldPresenter also cancels the async requests to avoid memory leaks. You should do that too. Otherwise your presenter can only be garbage collected after the retrofit httpcall has completed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM