Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recreate views on config changes #189

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions conductor/src/main/java/com/bluelinelabs/conductor/Controller.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public abstract class Controller {
private final ArrayList<String> requestedPermissions = new ArrayList<>();
private final ArrayList<RouterRequiringFunc> onRouterSetListeners = new ArrayList<>();
private WeakReference<View> destroyedView;
private boolean recreateViewOnConfigChange;

@NonNull
static Controller newInstance(@NonNull Bundle bundle) {
Expand Down Expand Up @@ -618,6 +619,33 @@ public void setRetainViewMode(@NonNull RetainViewMode retainViewMode) {
}
}

/**
* Returns whether this controller should recreate its view when configuration changes.
*
* @see #setRecreateViewOnConfigChange(boolean)
*/
public boolean isRecreateViewOnConfigChange() {
return recreateViewOnConfigChange;
}

/**
* Sets whether this Controller should recreate its view when configuration changes. This is
* typically necessary when the Activity has specified in the manifest that it will handle
* configuration changes such as an orientation change.
*
* For this method to have any effect, the Activity should set a value for {@code android:configChanges}
* in the manifest. Otherwise, the Activity will be destroyed and recreated, and there will be
* no need to explicitly recreate the view outside of that.
*
* If the top Controller has set this to {@code true} when a configuration change happens, that
* Controller's current view will be removed and a recreated view will be added to the parent.
* For Controllers that are lower in the stack, those Controllers' view will only be recreated
* if the Controller also has a {@link RetainViewMode} of {@link RetainViewMode#RETAIN_DETACH}.
*/
public void setRecreateViewOnConfigChange(boolean recreateViewOnConfigChange) {
this.recreateViewOnConfigChange = recreateViewOnConfigChange;
}

/**
* Returns the {@link ControllerChangeHandler} that should be used for pushing this Controller, or null
* if the handler from the {@link RouterTransaction} should be used instead.
Expand Down Expand Up @@ -902,6 +930,21 @@ final View inflate(@NonNull ViewGroup parent) {
removeViewReference();
}

return inflateAfterCheckingView(parent);
}

final View reinflate(@NonNull ViewGroup parent) {
if (view != null) {
detach(view, true, false);
removeViewReference();
}

view = null;

return inflateAfterCheckingView(parent);
}

private View inflateAfterCheckingView(@NonNull ViewGroup parent) {
if (view == null) {
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
Expand Down
35 changes: 35 additions & 0 deletions conductor/src/main/java/com/bluelinelabs/conductor/Router.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
Expand Down Expand Up @@ -560,6 +561,40 @@ public final boolean onOptionsItemSelected(@NonNull MenuItem item) {
return false;
}

/**
* @see Controller#setRecreateViewOnConfigChange(boolean)
*/
public final void onConfigurationChanged(Configuration newConfig) {
if (backstack.isEmpty()) {
return;
}

RouterTransaction topTransaction = backstack.peek();
Controller topController = topTransaction.controller;
if (topController.isRecreateViewOnConfigChange()) {
View currentView = topController.getView();
if (currentView != null) {
container.removeView(currentView);
}

View newView = topController.reinflate(container);
container.addView(newView);
}

for (RouterTransaction transaction : backstack) {
if (transaction == topTransaction) {
continue;
}

Controller controller = transaction.controller;
if (controller.isRecreateViewOnConfigChange()
&& controller.getRetainViewMode() == Controller.RetainViewMode.RETAIN_DETACH) {

controller.reinflate(container);
}
}
}

private void popToTransaction(@NonNull RouterTransaction transaction, @Nullable ControllerChangeHandler changeHandler) {
RouterTransaction topTransaction = backstack.peek();
List<RouterTransaction> poppedTransactions = backstack.popTo(transaction);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
Expand Down Expand Up @@ -261,6 +262,15 @@ public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);

for (Router router : routerMap.values()) {
router.onConfigurationChanged(newConfig);
}
}

public void registerForActivityResult(@NonNull String instanceId, int requestCode) {
activityRequestMap.put(requestCode, instanceId);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.bluelinelabs.conductor;

import android.view.ViewGroup;
import android.view.View;

import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.ListUtils;
Expand All @@ -13,10 +14,13 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

Expand Down Expand Up @@ -345,7 +349,7 @@ public void testChildRouterRearrangeTransactionBackstack() {
Controller parent = new TestController();
router.setRoot(RouterTransaction.with(parent));

Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
Router childRouter = parent.getChildRouter((ViewGroup) parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));

RouterTransaction transaction1 = RouterTransaction.with(new TestController());
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
Expand All @@ -371,4 +375,86 @@ public void testChildRouterRearrangeTransactionBackstack() {
assertEquals(0, childRouter.getBackstackSize());
}

public void testRecreateViewsOnConfigChange_notRecreatesTopControllerView() {
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());

List<RouterTransaction> backstack = new ArrayList<>();
backstack.add(rootTransaction);
backstack.add(topTransaction);

router.setBackstack(backstack, null);

View originalTopView = topTransaction.controller.getView();
router.onConfigurationChanged(null);
View newTopView = topTransaction.controller.getView();

assertNotNull(originalTopView);
assertNotNull(newTopView);
assertEquals(originalTopView, newTopView);
}

@Test
public void testRecreateViewsOnConfigChange_recreatesTopControllerView() {
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
topTransaction.controller.setRecreateViewOnConfigChange(true);

List<RouterTransaction> backstack = new ArrayList<>();
backstack.add(rootTransaction);
backstack.add(topTransaction);

router.setBackstack(backstack, null);

View originalTopView = topTransaction.controller.getView();
router.onConfigurationChanged(null);
View newTopView = topTransaction.controller.getView();

assertNotNull(originalTopView);
assertNotNull(newTopView);
assertNotEquals(originalTopView, newTopView);
}

@Test
public void testRecreateViewsOnConfigChange_notRecreatesLowerControllerView() {
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());

List<RouterTransaction> backstack = new ArrayList<>();
backstack.add(rootTransaction);
backstack.add(topTransaction);

router.setBackstack(backstack, null);

View originalRootView = rootTransaction.controller.getView();
router.onConfigurationChanged(null);
View newRootView = rootTransaction.controller.getView();

assertNotNull(originalRootView);
assertNotNull(newRootView);
assertEquals(originalRootView, newRootView);
}

@Test
public void testRecreateViewsOnConfigChange_recreatesLowerControllerView() {
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
rootTransaction.controller.setRecreateViewOnConfigChange(true);
rootTransaction.controller.setRetainViewMode(Controller.RetainViewMode.RETAIN_DETACH);

List<RouterTransaction> backstack = new ArrayList<>();
backstack.add(rootTransaction);
backstack.add(topTransaction);

router.setBackstack(backstack, null);

View originalRootView = rootTransaction.controller.getView();
router.onConfigurationChanged(null);
View newRootView = rootTransaction.controller.getView();

assertNotNull(originalRootView);
assertNotNull(newRootView);
assertNotEquals(originalRootView, newRootView);
}

}