Skip to content

Commit bc4bb1c

Browse files
committed
Add ControllerFactory.
ControllerFactory allows Controllers to be provided dependencies through their constructors after a configuration change. This means field injection is no longer a requirement when injecting dependencies into a Controller.
1 parent f72b5be commit bc4bb1c

File tree

7 files changed

+176
-12
lines changed

7 files changed

+176
-12
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,7 @@ public static Router attachRouter(@NonNull Activity activity, @NonNull ViewGroup
4141
return router;
4242
}
4343

44+
public static void setControllerFactory(@NonNull ControllerFactory factory) {
45+
Controller.FACTORY = factory;
46+
}
4447
}

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

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,30 +92,34 @@ public abstract class Controller {
9292
private boolean isPerformingExitTransition;
9393
private boolean isContextAvailable;
9494

95+
static volatile ControllerFactory FACTORY = DefaultControllerFactory.INSTANCE;
96+
9597
@NonNull
9698
static Controller newInstance(@NonNull Bundle bundle) {
9799
final String className = bundle.getString(KEY_CLASS_NAME);
98100
//noinspection ConstantConditions
99101
Class cls = ClassUtils.classForName(className, false);
100-
Constructor[] constructors = cls.getConstructors();
101-
Constructor bundleConstructor = getBundleConstructor(constructors);
102-
103102
Bundle args = bundle.getBundle(KEY_ARGS);
104103
if (args != null) {
105104
args.setClassLoader(cls.getClassLoader());
106105
}
107106

108107
Controller controller;
109108
try {
110-
if (bundleConstructor != null) {
111-
controller = (Controller)bundleConstructor.newInstance(args);
112-
} else {
113-
//noinspection ConstantConditions
114-
controller = (Controller)getDefaultConstructor(constructors).newInstance();
115-
116-
// Restore the args that existed before the last process death
117-
if (args != null) {
118-
controller.args.putAll(args);
109+
controller = FACTORY.create(className, args);
110+
if (controller == null) {
111+
Constructor[] constructors = cls.getConstructors();
112+
Constructor bundleConstructor = getBundleConstructor(constructors);
113+
if (bundleConstructor != null) {
114+
controller = (Controller) bundleConstructor.newInstance(args);
115+
} else {
116+
//noinspection ConstantConditions
117+
controller = (Controller) getDefaultConstructor(constructors).newInstance();
118+
119+
// Restore the args that existed before the last process death
120+
if (args != null) {
121+
controller.args.putAll(args);
122+
}
119123
}
120124
}
121125
} catch (Exception e) {
@@ -1321,6 +1325,10 @@ final void setParentController(@Nullable Controller controller) {
13211325
}
13221326

13231327
private void ensureRequiredConstructor() {
1328+
if (FACTORY != DefaultControllerFactory.INSTANCE) {
1329+
// We assume you know what you're doing if you've installed a ControllerFactory
1330+
return;
1331+
}
13241332
Constructor[] constructors = getClass().getConstructors();
13251333
if (getBundleConstructor(constructors) == null && getDefaultConstructor(constructors) == null) {
13261334
throw new RuntimeException(getClass() + " does not have a constructor that takes a Bundle argument or a default constructor. Controllers must have one of these in order to restore their states.");
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.bluelinelabs.conductor;
2+
3+
import android.os.Bundle;
4+
import androidx.annotation.NonNull;
5+
import androidx.annotation.Nullable;
6+
7+
public interface ControllerFactory {
8+
@Nullable
9+
Controller create(@NonNull String controllerName, @Nullable Bundle args);
10+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.bluelinelabs.conductor;
2+
3+
import android.os.Bundle;
4+
import androidx.annotation.NonNull;
5+
import androidx.annotation.Nullable;
6+
7+
final class DefaultControllerFactory implements ControllerFactory {
8+
9+
static final DefaultControllerFactory INSTANCE = new DefaultControllerFactory();
10+
11+
@Nullable
12+
@Override
13+
public final Controller create(@NonNull String controllerName, @Nullable Bundle args) {
14+
return null;
15+
}
16+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.bluelinelabs.conductor;
2+
3+
import android.os.Bundle;
4+
import com.bluelinelabs.conductor.util.ActivityProxy;
5+
import com.bluelinelabs.conductor.util.TestController;
6+
import com.bluelinelabs.conductor.util.TestControllerFactory;
7+
import com.bluelinelabs.conductor.util.TestDependenciesController;
8+
import org.junit.Before;
9+
import org.junit.Test;
10+
import org.junit.runner.RunWith;
11+
import org.robolectric.RobolectricTestRunner;
12+
import org.robolectric.annotation.Config;
13+
14+
import static org.junit.Assert.assertEquals;
15+
16+
@RunWith(RobolectricTestRunner.class)
17+
@Config(manifest = Config.NONE)
18+
public class ControllerFactoryTests {
19+
20+
private Router router;
21+
22+
private ActivityProxy activityProxy;
23+
24+
public void createActivityController(Bundle savedInstanceState, boolean includeStartAndResume) {
25+
activityProxy = new ActivityProxy().create(savedInstanceState);
26+
27+
if (includeStartAndResume) {
28+
activityProxy.start().resume();
29+
}
30+
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
31+
if (!router.hasRootController()) {
32+
router.setRoot(RouterTransaction.with(new TestController()));
33+
}
34+
}
35+
36+
@Before
37+
public void setup() {
38+
Conductor.setControllerFactory(new TestControllerFactory());
39+
createActivityController(null, true);
40+
}
41+
42+
@Test
43+
public void testControllerWithDependenciesRecreated() {
44+
Bundle args = new Bundle();
45+
args.putBoolean(TestDependenciesController.WAS_RECREATED_WITH_BUNDLE_ARGS, true);
46+
TestDependenciesController controller = new TestDependenciesController(args, false);
47+
router.pushController(RouterTransaction.with(controller)
48+
.tag("root"));
49+
activityProxy.getActivity().isChangingConfigurations = true;
50+
51+
assertEquals(false, controller.wasCreatedByFactory);
52+
assertEquals(false, controller.wasInstanceStateRestored);
53+
54+
Bundle bundle = new Bundle();
55+
activityProxy.saveInstanceState(bundle);
56+
activityProxy.pause();
57+
activityProxy.stop(true);
58+
activityProxy.destroy();
59+
60+
createActivityController(bundle, false);
61+
controller = (TestDependenciesController)router.getControllerWithTag("root");
62+
assertEquals(true, controller.wasCreatedByFactory);
63+
assertEquals(true, controller.wasInstanceStateRestored);
64+
assertEquals(true, controller.wasRecreatedWithBundleArgs);
65+
}
66+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.bluelinelabs.conductor.util;
2+
3+
import android.os.Bundle;
4+
5+
import com.bluelinelabs.conductor.Controller;
6+
import com.bluelinelabs.conductor.ControllerFactory;
7+
8+
import androidx.annotation.NonNull;
9+
import androidx.annotation.Nullable;
10+
11+
public class TestControllerFactory implements ControllerFactory {
12+
@Nullable
13+
@Override
14+
public Controller create(@NonNull String controllerName, @Nullable Bundle args) {
15+
if (controllerName.equals(TestDependenciesController.class.getName())) {
16+
return new TestDependenciesController(args, true);
17+
}
18+
return null;
19+
}
20+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.bluelinelabs.conductor.util;
2+
3+
import android.os.Bundle;
4+
import android.view.LayoutInflater;
5+
import android.view.View;
6+
import android.view.ViewGroup;
7+
8+
import com.bluelinelabs.conductor.Controller;
9+
10+
import androidx.annotation.NonNull;
11+
12+
public class TestDependenciesController extends Controller {
13+
14+
public static final String WAS_RECREATED_WITH_BUNDLE_ARGS = "WAS_RECREATED_WITH_BUNDLE_ARGS";
15+
private static final String INSTANCE_STATE = "INSTANCE_STATE";
16+
public final Boolean wasCreatedByFactory;
17+
public Boolean wasInstanceStateRestored = false;
18+
public final Boolean wasRecreatedWithBundleArgs;
19+
20+
public TestDependenciesController(Bundle args, Boolean wasCreatedByFactory) {
21+
super(args);
22+
this.wasRecreatedWithBundleArgs = args.getBoolean(WAS_RECREATED_WITH_BUNDLE_ARGS, false);
23+
this.wasCreatedByFactory = wasCreatedByFactory;
24+
}
25+
26+
@NonNull
27+
@Override
28+
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
29+
return new AttachFakingFrameLayout(inflater.getContext());
30+
}
31+
32+
@Override protected void onSaveInstanceState(@NonNull Bundle outState) {
33+
super.onSaveInstanceState(outState);
34+
outState.putBoolean(INSTANCE_STATE, true);
35+
}
36+
37+
@Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
38+
super.onRestoreInstanceState(savedInstanceState);
39+
wasInstanceStateRestored = savedInstanceState.getBoolean(INSTANCE_STATE);
40+
}
41+
}

0 commit comments

Comments
 (0)