Skip to content

Commit

Permalink
Add ControllerFactory.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
AngusMorton committed Mar 27, 2019
1 parent f72b5be commit bc4bb1c
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,7 @@ public static Router attachRouter(@NonNull Activity activity, @NonNull ViewGroup
return router;
}

public static void setControllerFactory(@NonNull ControllerFactory factory) {
Controller.FACTORY = factory;
}
}
32 changes: 20 additions & 12 deletions conductor/src/main/java/com/bluelinelabs/conductor/Controller.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,30 +92,34 @@ public abstract class Controller {
private boolean isPerformingExitTransition;
private boolean isContextAvailable;

static volatile ControllerFactory FACTORY = DefaultControllerFactory.INSTANCE;

@NonNull
static Controller newInstance(@NonNull Bundle bundle) {
final String className = bundle.getString(KEY_CLASS_NAME);
//noinspection ConstantConditions
Class cls = ClassUtils.classForName(className, false);
Constructor[] constructors = cls.getConstructors();
Constructor bundleConstructor = getBundleConstructor(constructors);

Bundle args = bundle.getBundle(KEY_ARGS);
if (args != null) {
args.setClassLoader(cls.getClassLoader());
}

Controller controller;
try {
if (bundleConstructor != null) {
controller = (Controller)bundleConstructor.newInstance(args);
} else {
//noinspection ConstantConditions
controller = (Controller)getDefaultConstructor(constructors).newInstance();

// Restore the args that existed before the last process death
if (args != null) {
controller.args.putAll(args);
controller = FACTORY.create(className, args);
if (controller == null) {
Constructor[] constructors = cls.getConstructors();
Constructor bundleConstructor = getBundleConstructor(constructors);
if (bundleConstructor != null) {
controller = (Controller) bundleConstructor.newInstance(args);
} else {
//noinspection ConstantConditions
controller = (Controller) getDefaultConstructor(constructors).newInstance();

// Restore the args that existed before the last process death
if (args != null) {
controller.args.putAll(args);
}
}
}
} catch (Exception e) {
Expand Down Expand Up @@ -1321,6 +1325,10 @@ final void setParentController(@Nullable Controller controller) {
}

private void ensureRequiredConstructor() {
if (FACTORY != DefaultControllerFactory.INSTANCE) {
// We assume you know what you're doing if you've installed a ControllerFactory
return;
}
Constructor[] constructors = getClass().getConstructors();
if (getBundleConstructor(constructors) == null && getDefaultConstructor(constructors) == null) {
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.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.bluelinelabs.conductor;

import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public interface ControllerFactory {
@Nullable
Controller create(@NonNull String controllerName, @Nullable Bundle args);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.bluelinelabs.conductor;

import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

final class DefaultControllerFactory implements ControllerFactory {

static final DefaultControllerFactory INSTANCE = new DefaultControllerFactory();

@Nullable
@Override
public final Controller create(@NonNull String controllerName, @Nullable Bundle args) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.bluelinelabs.conductor;

import android.os.Bundle;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.TestController;
import com.bluelinelabs.conductor.util.TestControllerFactory;
import com.bluelinelabs.conductor.util.TestDependenciesController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import static org.junit.Assert.assertEquals;

@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ControllerFactoryTests {

private Router router;

private ActivityProxy activityProxy;

public void createActivityController(Bundle savedInstanceState, boolean includeStartAndResume) {
activityProxy = new ActivityProxy().create(savedInstanceState);

if (includeStartAndResume) {
activityProxy.start().resume();
}
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(new TestController()));
}
}

@Before
public void setup() {
Conductor.setControllerFactory(new TestControllerFactory());
createActivityController(null, true);
}

@Test
public void testControllerWithDependenciesRecreated() {
Bundle args = new Bundle();
args.putBoolean(TestDependenciesController.WAS_RECREATED_WITH_BUNDLE_ARGS, true);
TestDependenciesController controller = new TestDependenciesController(args, false);
router.pushController(RouterTransaction.with(controller)
.tag("root"));
activityProxy.getActivity().isChangingConfigurations = true;

assertEquals(false, controller.wasCreatedByFactory);
assertEquals(false, controller.wasInstanceStateRestored);

Bundle bundle = new Bundle();
activityProxy.saveInstanceState(bundle);
activityProxy.pause();
activityProxy.stop(true);
activityProxy.destroy();

createActivityController(bundle, false);
controller = (TestDependenciesController)router.getControllerWithTag("root");
assertEquals(true, controller.wasCreatedByFactory);
assertEquals(true, controller.wasInstanceStateRestored);
assertEquals(true, controller.wasRecreatedWithBundleArgs);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.bluelinelabs.conductor.util;

import android.os.Bundle;

import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.ControllerFactory;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class TestControllerFactory implements ControllerFactory {
@Nullable
@Override
public Controller create(@NonNull String controllerName, @Nullable Bundle args) {
if (controllerName.equals(TestDependenciesController.class.getName())) {
return new TestDependenciesController(args, true);
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.bluelinelabs.conductor.util;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.bluelinelabs.conductor.Controller;

import androidx.annotation.NonNull;

public class TestDependenciesController extends Controller {

public static final String WAS_RECREATED_WITH_BUNDLE_ARGS = "WAS_RECREATED_WITH_BUNDLE_ARGS";
private static final String INSTANCE_STATE = "INSTANCE_STATE";
public final Boolean wasCreatedByFactory;
public Boolean wasInstanceStateRestored = false;
public final Boolean wasRecreatedWithBundleArgs;

public TestDependenciesController(Bundle args, Boolean wasCreatedByFactory) {
super(args);
this.wasRecreatedWithBundleArgs = args.getBoolean(WAS_RECREATED_WITH_BUNDLE_ARGS, false);
this.wasCreatedByFactory = wasCreatedByFactory;
}

@NonNull
@Override
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return new AttachFakingFrameLayout(inflater.getContext());
}

@Override protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(INSTANCE_STATE, true);
}

@Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
wasInstanceStateRestored = savedInstanceState.getBoolean(INSTANCE_STATE);
}
}

0 comments on commit bc4bb1c

Please sign in to comment.