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

Add ControllerFactory. #513

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
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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  Controller shouldn't know about any factory


@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);
AngusMorton marked this conversation as resolved.
Show resolved Hide resolved
}
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 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think DefaultControllerFactory should implement default logic for creating Controller, but not just be a stub in logic

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could only do that without extra reflection if the first argument for create was a Class.


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);
}
}