MVP and RxJava - Handling Orientation Changes on Android

3.1k views Asked by At

Im using MVP and RxJava similar to google-samples repo.

And I would like to ask how to correctly handle screen orientation change.

2

There are 2 answers

0
R. Zagórski On BEST ANSWER

There is another strategy that enables saving presenter state and also Observable's state: retain Fragment. This way you omit standard Android way of saving data into Bundle (which enables only to save simple variables and not the state of network requests.)

Activity:

public class MainActivity extends AppCompatActivity implements MainActivityView {
    private static final String TAG_RETAIN_FRAGMENT = "retain_fragment";

    MainActivityPresenter mPresenter;

    private MainActivityRetainFragment mRetainFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        initRetainFragment();
        initPresenter();
    }

    private void initRetainFragment() {
        FragmentManager fm = getSupportFragmentManager();
        mRetainFragment = (MainActivityRetainFragment) fm.findFragmentByTag(TAG_RETAIN_FRAGMENT);
        if (mRetainFragment == null) {
            mRetainFragment = new MainActivityRetainFragment();
            fm.beginTransaction().add(mRetainFragment, TAG_RETAIN_FRAGMENT).commit();
        }
    }

    private void initPresenter() {
        mPresenter = mRetainFragment.getPresenter();
        mRetainFragment.retainPresenter(null);
        if (mPresenter == null) {
            mPresenter = new MainActivityPresenter();
        }
        mPresenter.attachView(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (!isFinishing()) {
            mRetainFragment.retainPresenter(mPresenter);
            return;
        }
        mPresenter.detachView();
        mPresenter = null;
    }
}

Retain Fragment:

public class MainActivityRetainFragment extends Fragment {
    private MainActivityPresenter presenter;

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

    public void retainPresenter(MainActivityPresenter presenter) {
        this.presenter = presenter;
    }

    public MainActivityPresenter getPresenter() {
        return presenter;
    }
}

Notice the way activity lifecycle events are handled. When the Activity is created, retain Fragment is added to the backstack and on lifecycle events it is restored from backstack. retain Fragment does not have any view, it is just a holder for presenter during configuration changes. Notice the main invocation that enables restoring exactly the same fragment (and it's content) from backstack:

setRetainInstance(true)

If you are concerned about memory leaks: every time the presenter is restored presenter's view is restored:

mPresenter.attachView(this);

So the previous Activity reference is replaced by new one.

More about such handling of configuration changes here here

1
Prakash On

I handled by encapsulating view's state in specific ViewState class in presenter, and it is easy to test.

public interface BaseViewState {
    void saveState(@NonNull Bundle outState);

    void restoreState(@Nullable Bundle savedInstanceState);
}

class HomeViewState implements BaseViewState {

    static final long NONE_NUM = -1;

    static final String STATE_COMIC_NUM = "state_comic_num";

    private long comicNum = NONE_NUM;

    @Inject
    HomeViewState() {
    }

    @Override
    public void saveState(@NonNull Bundle outState) {
        outState.putLong(STATE_COMIC_NUM, comicNum);
    }

    @Override
    public void restoreState(@Nullable Bundle savedInstanceState) {
        if (savedInstanceState != null) {
            comicNum = savedInstanceState.getLong(STATE_COMIC_NUM, NONE_NUM);
        }
    }

    long getComicNumber() {
        return comicNum;
    }

    void setComicNum(long comicNum) {
        this.comicNum = comicNum;
    }
}

get/set values from viewState in presenter, this helps to keep it updated, as well as presenter stateless.

public class HomePresenter implements HomeContract.Presenter {

    private HomeViewState viewState;

    HomeViewState getViewState() {
        return viewState;
    }

    @Override
    public void loadComic() {
       loadComic(viewState.getComicNumber());
    }
    ...
}

in Activity as View should initiate call to save and restore.

public class MainActivity extends BaseActivity implements HomeContract.View {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ...
            homePresenter.getViewState().restoreState(savedInstanceState);
        }


        @Override
        public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
            super.onSaveInstanceState(outState, outPersistentState);

            homePresenter.getViewState().saveState(outState);
        }
     ...
}