Skip to content

Commit e2be5e8

Browse files
author
Chris Hanson
committed
Recreate views on config changes
This is typically necessary when the Activity has specified in the manifest that it will handle configuration changes such as an orientation change. If the top Controller.setRecreateViewOnConfigChange(true) has been called before 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 RetainViewMode of RetainViewMode#RETAIN_DETACH.
1 parent f4ef47c commit e2be5e8

File tree

4 files changed

+175
-1
lines changed

4 files changed

+175
-1
lines changed

conductor/src/main/java/com/bluelinelabs/conductor/Controller.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public abstract class Controller {
8686
private final ArrayList<String> requestedPermissions = new ArrayList<>();
8787
private final ArrayList<RouterRequiringFunc> onRouterSetListeners = new ArrayList<>();
8888
private WeakReference<View> destroyedView;
89+
private boolean recreateViewOnConfigChange;
8990

9091
@NonNull
9192
static Controller newInstance(@NonNull Bundle bundle) {
@@ -618,6 +619,33 @@ public void setRetainViewMode(@NonNull RetainViewMode retainViewMode) {
618619
}
619620
}
620621

622+
/**
623+
* Returns whether this controller should recreate its view when configuration changes.
624+
*
625+
* @see #setRecreateViewOnConfigChange(boolean)
626+
*/
627+
public boolean isRecreateViewOnConfigChange() {
628+
return recreateViewOnConfigChange;
629+
}
630+
631+
/**
632+
* Sets whether this Controller should recreate its view when configuration changes. This is
633+
* typically necessary when the Activity has specified in the manifest that it will handle
634+
* configuration changes such as an orientation change.
635+
*
636+
* For this method to have any effect, the Activity should set a value for {@code android:configChanges}
637+
* in the manifest. Otherwise, the Activity will be destroyed and recreated, and there will be
638+
* no need to explicitly recreate the view outside of that.
639+
*
640+
* If the top Controller has set this to {@code true} when a configuration change happens, that
641+
* Controller's current view will be removed and a recreated view will be added to the parent.
642+
* For Controllers that are lower in the stack, those Controllers' view will only be recreated
643+
* if the Controller also has a {@link RetainViewMode} of {@link RetainViewMode#RETAIN_DETACH}.
644+
*/
645+
public void setRecreateViewOnConfigChange(boolean recreateViewOnConfigChange) {
646+
this.recreateViewOnConfigChange = recreateViewOnConfigChange;
647+
}
648+
621649
/**
622650
* Returns the {@link ControllerChangeHandler} that should be used for pushing this Controller, or null
623651
* if the handler from the {@link RouterTransaction} should be used instead.
@@ -902,6 +930,21 @@ final View inflate(@NonNull ViewGroup parent) {
902930
removeViewReference();
903931
}
904932

933+
return inflateAfterCheckingView(parent);
934+
}
935+
936+
final View reinflate(@NonNull ViewGroup parent) {
937+
if (view != null) {
938+
detach(view, true, false);
939+
removeViewReference();
940+
}
941+
942+
view = null;
943+
944+
return inflateAfterCheckingView(parent);
945+
}
946+
947+
private View inflateAfterCheckingView(@NonNull ViewGroup parent) {
905948
if (view == null) {
906949
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
907950
for (LifecycleListener lifecycleListener : listeners) {

conductor/src/main/java/com/bluelinelabs/conductor/Router.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import android.app.Activity;
44
import android.content.Intent;
5+
import android.content.res.Configuration;
56
import android.os.Bundle;
67
import android.support.annotation.NonNull;
78
import android.support.annotation.Nullable;
@@ -560,6 +561,40 @@ public final boolean onOptionsItemSelected(@NonNull MenuItem item) {
560561
return false;
561562
}
562563

564+
/**
565+
* @see Controller#setRecreateViewOnConfigChange(boolean)
566+
*/
567+
public final void onConfigurationChanged(Configuration newConfig) {
568+
if (backstack.isEmpty()) {
569+
return;
570+
}
571+
572+
RouterTransaction topTransaction = backstack.peek();
573+
Controller topController = topTransaction.controller;
574+
if (topController.isRecreateViewOnConfigChange()) {
575+
View currentView = topController.getView();
576+
if (currentView != null) {
577+
container.removeView(currentView);
578+
}
579+
580+
View newView = topController.reinflate(container);
581+
container.addView(newView);
582+
}
583+
584+
for (RouterTransaction transaction : backstack) {
585+
if (transaction == topTransaction) {
586+
continue;
587+
}
588+
589+
Controller controller = transaction.controller;
590+
if (controller.isRecreateViewOnConfigChange()
591+
&& controller.getRetainViewMode() == Controller.RetainViewMode.RETAIN_DETACH) {
592+
593+
controller.reinflate(container);
594+
}
595+
}
596+
}
597+
563598
private void popToTransaction(@NonNull RouterTransaction transaction, @Nullable ControllerChangeHandler changeHandler) {
564599
RouterTransaction topTransaction = backstack.peek();
565600
List<RouterTransaction> poppedTransactions = backstack.popTo(transaction);

conductor/src/main/java/com/bluelinelabs/conductor/internal/LifecycleHandler.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import android.app.Fragment;
77
import android.content.Context;
88
import android.content.Intent;
9+
import android.content.res.Configuration;
910
import android.os.Build;
1011
import android.os.Bundle;
1112
import android.os.Parcel;
@@ -261,6 +262,15 @@ public boolean onOptionsItemSelected(MenuItem item) {
261262
return super.onOptionsItemSelected(item);
262263
}
263264

265+
@Override
266+
public void onConfigurationChanged(Configuration newConfig) {
267+
super.onConfigurationChanged(newConfig);
268+
269+
for (Router router : routerMap.values()) {
270+
router.onConfigurationChanged(newConfig);
271+
}
272+
}
273+
264274
public void registerForActivityResult(@NonNull String instanceId, int requestCode) {
265275
activityRequestMap.put(requestCode, instanceId);
266276
}

conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.bluelinelabs.conductor;
22

33
import android.view.ViewGroup;
4+
import android.view.View;
45

56
import com.bluelinelabs.conductor.util.ActivityProxy;
67
import com.bluelinelabs.conductor.util.ListUtils;
@@ -13,10 +14,13 @@
1314
import org.robolectric.RobolectricTestRunner;
1415
import org.robolectric.annotation.Config;
1516

17+
import java.util.ArrayList;
1618
import java.util.List;
1719

1820
import static org.junit.Assert.assertEquals;
1921
import static org.junit.Assert.assertFalse;
22+
import static org.junit.Assert.assertNotEquals;
23+
import static org.junit.Assert.assertNotNull;
2024
import static org.junit.Assert.assertNull;
2125
import static org.junit.Assert.assertTrue;
2226

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

348-
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
352+
Router childRouter = parent.getChildRouter((ViewGroup) parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
349353

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

378+
public void testRecreateViewsOnConfigChange_notRecreatesTopControllerView() {
379+
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
380+
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
381+
382+
List<RouterTransaction> backstack = new ArrayList<>();
383+
backstack.add(rootTransaction);
384+
backstack.add(topTransaction);
385+
386+
router.setBackstack(backstack, null);
387+
388+
View originalTopView = topTransaction.controller.getView();
389+
router.onConfigurationChanged(null);
390+
View newTopView = topTransaction.controller.getView();
391+
392+
assertNotNull(originalTopView);
393+
assertNotNull(newTopView);
394+
assertEquals(originalTopView, newTopView);
395+
}
396+
397+
@Test
398+
public void testRecreateViewsOnConfigChange_recreatesTopControllerView() {
399+
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
400+
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
401+
topTransaction.controller.setRecreateViewOnConfigChange(true);
402+
403+
List<RouterTransaction> backstack = new ArrayList<>();
404+
backstack.add(rootTransaction);
405+
backstack.add(topTransaction);
406+
407+
router.setBackstack(backstack, null);
408+
409+
View originalTopView = topTransaction.controller.getView();
410+
router.onConfigurationChanged(null);
411+
View newTopView = topTransaction.controller.getView();
412+
413+
assertNotNull(originalTopView);
414+
assertNotNull(newTopView);
415+
assertNotEquals(originalTopView, newTopView);
416+
}
417+
418+
@Test
419+
public void testRecreateViewsOnConfigChange_notRecreatesLowerControllerView() {
420+
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
421+
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
422+
423+
List<RouterTransaction> backstack = new ArrayList<>();
424+
backstack.add(rootTransaction);
425+
backstack.add(topTransaction);
426+
427+
router.setBackstack(backstack, null);
428+
429+
View originalRootView = rootTransaction.controller.getView();
430+
router.onConfigurationChanged(null);
431+
View newRootView = rootTransaction.controller.getView();
432+
433+
assertNotNull(originalRootView);
434+
assertNotNull(newRootView);
435+
assertEquals(originalRootView, newRootView);
436+
}
437+
438+
@Test
439+
public void testRecreateViewsOnConfigChange_recreatesLowerControllerView() {
440+
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
441+
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
442+
rootTransaction.controller.setRecreateViewOnConfigChange(true);
443+
rootTransaction.controller.setRetainViewMode(Controller.RetainViewMode.RETAIN_DETACH);
444+
445+
List<RouterTransaction> backstack = new ArrayList<>();
446+
backstack.add(rootTransaction);
447+
backstack.add(topTransaction);
448+
449+
router.setBackstack(backstack, null);
450+
451+
View originalRootView = rootTransaction.controller.getView();
452+
router.onConfigurationChanged(null);
453+
View newRootView = rootTransaction.controller.getView();
454+
455+
assertNotNull(originalRootView);
456+
assertNotNull(newRootView);
457+
assertNotEquals(originalRootView, newRootView);
458+
}
459+
374460
}

0 commit comments

Comments
 (0)