Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic types and polymorphism

I have BaseFragment:

    public abstract class BaseFragment extends Fragment implements BaseMvpView {

        private BasePresenter presenter;

        protected void syncLifeCycle(BasePresenter presenter) {
            this.presenter = presenter;
            this.presenter.onCreate();
        }

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

            //noinspection unchecked
            presenter.onAttachView(this); //it works with a warning
        }

        @Override
        public void onResume() {
            super.onResume();
            presenter.onResume();
        }

        @Override
        public void onPause() {
            super.onPause();
            presenter.onPause();
        }

        @Override
        public void onDestroyView() {
            super.onDestroyView();
            presenter.onDetachView();
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            presenter.onDestroy();
        }

        @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            presenter.onActivityResult(requestCode, resultCode, data);
    }
}  

and many classes that extends it. For example MainFragment:

    public class MainFragment extends BaseFragment implements MainMvpView {

        MainPresenter<MainMvpView> presenter;

        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            syncLifeCycle(presenter);
            //presenter.onCreate();
        }

        @Override
        public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            //presenter.onAttachView(this);
        }

        @Override
        public void onResume() {
            super.onResume();
            //presenter.onResume();
        }

        @Override
        public void onPause() {
            super.onPause();
            //presenter.onPause();
        }

        @Override
        public void onDestroyView() {
            super.onDestroyView();
            //presenter.onDetachView();
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            //presenter.onDestroy();
        }

        @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            //presenter.onActivityResult(requestCode, resultCode, data);
        }
}   

I want to avoid repeating the life cycle synchronization code of each fragment and presenter. Therefore I want to implement this process in the BaseFragment. In Java this line presenter.onAttachView(this); works but with a warning "Unchecked call onAttachView(V)"(I can live with this). But Kotlin does not allow me to do this at all

abstract class BaseFragmentKotlin : Fragment(), BaseMvpView {

    private var presenter: BasePresenter<*>? = null

    //...

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        presenter?.onAttachView(this) //Does not work. "Out-projected type 'BasePresenter<*>?' prohibits the use of 'public abstract fun onAttachView(mvpView: V!): Unit defined in com.example.test.BasePresenter"
    }

//...
}

I really need advice on how to do this correctly.

Edited:

    public class BasePresenterImpl<V extends BaseMvpView> implements BasePresenter<V> {

        @Nullable
        public V mvpView;

        @Override
        public void onCreate() {

        }

        @Override
        public void onAttachView(V mvpView) {
            this.mvpView = mvpView;
        }

        @Override
        public void onResume() {

        }

        @Override
        public void onPause() {

        }

        @Override
        public void onDetachView() {
        mvpView = null;
        }

        @Override
        public void onDestroy() {

        }

        @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {

        }
}

Here is the whole test code https://github.com/AlexNikolaTest/Test/tree/master/app/src/main/java/com/example/mytest

like image 608
Alex Nik Avatar asked Nov 14 '17 09:11

Alex Nik


2 Answers

You could try this, then you can have the unchecked warning in Kotlin as well ;-)

    if (presenter != null) {
        val p = presenter as BasePresenter<BaseMvpView>
        p.onAttachView(this)
    }

In your MainFragment

    syncLifeCycle(presenter as BasePresenter<BaseMvpView>)

Not sure if it works, just played around a bit in IntelliJ. But since generics are erased during compilation and casting a MainPresenter to BasePresenter should also be fine, there is a good chance it goes through.

like image 114
Alex AIT Avatar answered Sep 22 '22 11:09

Alex AIT


I think replacing the star-projection with BaseMvpView would help

abstract class BaseFragmentKotlin : Fragment(), BaseMvpView {

    private var presenter: BasePresenter<BaseMvpView>? = null

    //...

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        presenter?.onAttachView(this)
    }

//...
}

The reason is that Kotlin distinguishes between out and in type parameters (also known as covariant and contravariant parameter types, respectively).

in type parameters state that the type parameter is to be consumed by the generic class, i.e. to be used as a parameter of a function, while out type parameters state that the generic class will produce a value of passed type parameter, i.e. to be used as a return type for some function.

the onAttachView(V mvpView) takes a contravariant type parameter which implies that it is not allowed for V to be of any type (it has to be of type BaseMvpView or a subclass) since you are consuming that value. That is, if V was totally unknown, we cannot safely read the parameter since V is expected to be an instance of BaseMvpView. However, if it were the case that onAttachView is producing ,i.e. returning, V object then star-projection would work.

Hope this helps!

like image 36
Omar El Halabi Avatar answered Sep 22 '22 11:09

Omar El Halabi