Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVP and RxJava - Handling Orientation Changes on Android

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

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

like image 323
MobileDev Avatar asked Dec 20 '16 21:12

MobileDev


People also ask

What happens when orientation changes android?

When you rotate your device and the screen changes orientation, Android usually destroys your application's existing Activities and Fragments and recreates them. Android does this so that your application can reload resources based on the new configuration.

How do I manage different orientations in android?

If you want to manually handle orientation changes in your app you must declare the "orientation" , "screenSize" , and "screenLayout" values in the android:configChanges attributes. You can declare multiple configuration values in the attribute by separating them with a pipe | character.

How do I stop android from restarting activity when changing orientations?

If you want the activity to not restart during screen orientation change, you can use the below AndroidManifest. xml. Please note the activity android:configChanges=”orientation|screenSize” attribute. This attribute makes the activity not restart when change screen orientation.

How do you change orientation when retaining data?

Another most common solution to dealing with orientation changes by setting the android:configChanges flag on your Activity in AndroidManifest. xml. Using this attribute your Activities won't be recreated and all your views and data will still be there after orientation change.


2 Answers

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

like image 163
R. Zagórski Avatar answered Sep 23 '22 10:09

R. Zagórski


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);
        }
     ...
}
like image 31
Prakash Avatar answered Sep 22 '22 10:09

Prakash