Compare commits

..

5 Commits

Author SHA1 Message Date
Eric Kuck 58d1f37db0 Test fix 2016-03-21 22:35:51 -05:00
Eric Kuck 7c175e4ce5 Fixed where the Controller's view is set internally 2016-03-21 22:30:56 -05:00
Eric Kuck 328b8a0873 Readme update 2016-03-21 11:55:46 -05:00
Eric Kuck db446279bb Added some basic lint checks 2016-03-21 11:30:13 -05:00
Eric Kuck 80042ea71d Minor demo app refactor 2016-03-20 00:04:22 -05:00
18 changed files with 289 additions and 21 deletions
+3 -3
View File
@@ -20,14 +20,14 @@ Conductor is architecture-agnostic and does not try to force any design decision
## Installation
```gradle
compile 'com.bluelinelabs:conductor:1.0.2'
compile 'com.bluelinelabs:conductor:1.0.3'
// If you want the components that go along with
// Android's support libraries (currently just a PagerAdapter):
compile 'com.bluelinelabs:conductor-support:1.0.2'
compile 'com.bluelinelabs:conductor-support:1.0.3'
// If you want RxJava/RxAndroid lifecycle support:
compile 'com.bluelinelabs:conductor-rxlifecycle:1.0.2'
compile 'com.bluelinelabs:conductor-rxlifecycle:1.0.3'
```
## Components to Know
+20
View File
@@ -0,0 +1,20 @@
apply plugin: 'java'
configurations {
lintChecks
}
dependencies {
compile rootProject.ext.lintapi
compile rootProject.ext.lintchecks
lintChecks files(jar)
}
jar {
manifest {
attributes('Lint-Registry': 'com.bluelinelabs.conductor.lint.IssueRegistry')
}
}
apply from: rootProject.file('dependencies.gradle')
@@ -0,0 +1,100 @@
package com.bluelinelabs.conductor.lint;
import com.android.annotations.NonNull;
import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.List;
import lombok.ast.ClassDeclaration;
import lombok.ast.ConstructorDeclaration;
import lombok.ast.Node;
import lombok.ast.NormalTypeBody;
import lombok.ast.StrictListAccessor;
import lombok.ast.TypeMember;
import lombok.ast.VariableDefinition;
public final class ControllerChangeHandlerIssueDetector extends Detector implements Detector.JavaScanner, Detector.ClassScanner {
public static final Issue ISSUE =
Issue.create("ValidControllerChangeHandler", "ControllerChangeHandler not instantiatable",
"Non-abstract ControllerChangeHandler instances must have a default constructor for the"
+ " system to re-create them in the case of the process being killed.",
Category.CORRECTNESS, 6, Severity.FATAL,
new Implementation(ControllerChangeHandlerIssueDetector.class, Scope.JAVA_FILE_SCOPE));
public ControllerChangeHandlerIssueDetector() { }
@NonNull
@Override
public Speed getSpeed() {
return Speed.FAST;
}
@Override
public List<String> applicableSuperClasses() {
return Collections.singletonList("com.bluelinelabs.conductor.ControllerChangeHandler");
}
@Override
public void checkClass(@NonNull JavaContext context, ClassDeclaration node,
@NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
if (node == null) {
return;
}
final int flags = node.astModifiers().getEffectiveModifierFlags();
if ((flags & Modifier.ABSTRACT) != 0) {
return;
}
if ((flags & Modifier.PUBLIC) == 0) {
String message = String.format("This ControllerChangeHandler class should be public (%1$s)", cls.getName());
context.report(ISSUE, node, context.getLocation(node.astName()), message);
return;
}
if (cls.getContainingClass() != null && (flags & Modifier.STATIC) == 0) {
String message = String.format("This ControllerChangeHandler inner class should be static (%1$s)", cls.getName());
context.report(ISSUE, node, context.getLocation(node.astName()), message);
return;
}
boolean hasConstructor = false;
boolean hasDefaultConstructor = false;
NormalTypeBody body = node.astBody();
if (body != null) {
for (TypeMember member : body.astMembers()) {
if (member instanceof ConstructorDeclaration) {
hasConstructor = true;
ConstructorDeclaration constructor = (ConstructorDeclaration)member;
if (constructor.astModifiers().isPublic()) {
StrictListAccessor<VariableDefinition, ConstructorDeclaration> params = constructor.astParameters();
if (params.isEmpty()) {
hasDefaultConstructor = true;
break;
}
}
}
}
}
if (hasConstructor && !hasDefaultConstructor) {
String message = String.format(
"This ControllerChangeHandler needs to have a public default constructor (`%1$s`)",
cls.getName());
context.report(ISSUE, node, context.getLocation(node.astName()), message);
}
}
}
@@ -0,0 +1,108 @@
package com.bluelinelabs.conductor.lint;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.List;
import lombok.ast.ClassDeclaration;
import lombok.ast.ConstructorDeclaration;
import lombok.ast.Node;
import lombok.ast.NormalTypeBody;
import lombok.ast.StrictListAccessor;
import lombok.ast.TypeMember;
import lombok.ast.VariableDefinition;
public final class ControllerIssueDetector extends Detector implements Detector.JavaScanner, Detector.ClassScanner {
public static final Issue ISSUE =
Issue.create("ValidController", "Controller not instantiatable",
"Non-abstract Controller instances must have a default or single-argument constructor"
+ " that takes a Bundle in order for the system to re-create them in the"
+ " case of the process being killed.", Category.CORRECTNESS, 6, Severity.FATAL,
new Implementation(ControllerIssueDetector.class, Scope.JAVA_FILE_SCOPE));
public ControllerIssueDetector() { }
@NonNull
@Override
public Speed getSpeed() {
return Speed.FAST;
}
@Override
public List<String> applicableSuperClasses() {
return Collections.singletonList("com.bluelinelabs.conductor.Controller");
}
@Override
public void checkClass(@NonNull JavaContext context, ClassDeclaration node,
@NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
if (node == null) {
return;
}
final int flags = node.astModifiers().getEffectiveModifierFlags();
if ((flags & Modifier.ABSTRACT) != 0) {
return;
}
if ((flags & Modifier.PUBLIC) == 0) {
String message = String.format("This Controller class should be public (%1$s)", cls.getName());
context.report(ISSUE, node, context.getLocation(node.astName()), message);
return;
}
if (cls.getContainingClass() != null && (flags & Modifier.STATIC) == 0) {
String message = String.format("This Controller inner class should be static (%1$s)", cls.getName());
context.report(ISSUE, node, context.getLocation(node.astName()), message);
return;
}
boolean hasConstructor = false;
boolean hasDefaultConstructor = false;
boolean hasBundleConstructor = false;
NormalTypeBody body = node.astBody();
if (body != null) {
for (TypeMember member : body.astMembers()) {
if (member instanceof ConstructorDeclaration) {
hasConstructor = true;
ConstructorDeclaration constructor = (ConstructorDeclaration)member;
if (constructor.astModifiers().isPublic()) {
StrictListAccessor<VariableDefinition, ConstructorDeclaration> params = constructor.astParameters();
if (params.isEmpty()) {
hasDefaultConstructor = true;
break;
} else if (params.size() == 1 &&
(params.first().astTypeReference().getTypeName().equals(SdkConstants.CLASS_BUNDLE)) ||
params.first().astTypeReference().getTypeName().equals("Bundle")) {
hasBundleConstructor = true;
break;
}
}
}
}
}
if (hasConstructor && !hasDefaultConstructor && !hasBundleConstructor) {
String message = String.format(
"This Controller needs to have either a public default constructor or a" +
" public single-argument constructor that takes a Bundle. (`%1$s`)",
cls.getName());
context.report(ISSUE, node, context.getLocation(node.astName()), message);
}
}
}
@@ -0,0 +1,14 @@
package com.bluelinelabs.conductor.lint;
import com.android.tools.lint.detector.api.Issue;
import java.util.Arrays;
import java.util.List;
public final class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
@Override public List<Issue> getIssues() {
return Arrays.asList(
ControllerIssueDetector.ISSUE,
ControllerChangeHandlerIssueDetector.ISSUE);
}
}
+18
View File
@@ -31,11 +31,17 @@ android {
}
}
configurations {
lintChecks
}
dependencies {
testCompile rootProject.ext.junit
testCompile rootProject.ext.roboelectric
compile rootProject.ext.supportAnnotations
lintChecks project(path: ':conductor-lint', configuration: 'lintChecks')
}
unMock {
@@ -46,6 +52,18 @@ unMock {
keep "android.text.TextUtils"
}
task copyLintJar(type: Copy) {
from(configurations.lintChecks) {
rename { 'lint.jar' }
}
into 'build/intermediates/lint/'
}
project.afterEvaluate {
def compileLintTask = project.tasks.find { it.name == 'compileLint' }
compileLintTask.dependsOn(copyLintJar)
}
ext.artifactId = 'conductor'
apply from: rootProject.file('dependencies.gradle')
@@ -637,7 +637,6 @@ public abstract class Controller {
mAttached = true;
mNeedsAttach = false;
mView = view;
for (ChildControllerTransaction child : mChildControllers) {
attachChildController(child, new SimpleSwapChangeHandler());
@@ -850,6 +849,8 @@ public abstract class Controller {
lifecycleListener.preBindView(this, view);
}
mView = view;
onBindView(view);
view.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
@@ -180,7 +180,7 @@ public class ControllerTests {
controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH);
Assert.assertNull(controller.getView());
View view = controller.inflate(new FrameLayout(mRouter.getActivity()));
Assert.assertNull(controller.getView());
Assert.assertNotNull(controller.getView());
ViewUtils.setAttached(view, true);
Assert.assertNotNull(controller.getView());
ViewUtils.setAttached(view, false);
@@ -189,7 +189,7 @@ public class ControllerTests {
// Test View getting retained w/ RETAIN_DETACH
controller.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
view = controller.inflate(new FrameLayout(mRouter.getActivity()));
Assert.assertNull(controller.getView());
Assert.assertNotNull(controller.getView());
ViewUtils.setAttached(view, true);
Assert.assertNotNull(controller.getView());
ViewUtils.setAttached(view, false);
@@ -279,7 +279,7 @@ public class ControllerTests {
void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener);
}
static class ChangeHandler extends ControllerChangeHandler {
public static class ChangeHandler extends ControllerChangeHandler {
private ChangeHandlerListener mListener;
+1 -1
View File
@@ -7,7 +7,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:name="com.bluelinelabs.conductor.demo.App"
android:name=".DemoApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@@ -5,7 +5,7 @@ import android.app.Application;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
public class App extends Application {
public class DemoApplication extends Application {
public static RefWatcher refWatcher;
@@ -8,7 +8,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.bluelinelabs.conductor.demo.BundleBuilder;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
@@ -9,7 +9,7 @@ import android.widget.TextView;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.BundleBuilder;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.util.ColorUtil;
@@ -7,7 +7,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.bluelinelabs.conductor.demo.BundleBuilder;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
@@ -19,7 +19,7 @@ import com.bluelinelabs.conductor.changehandler.CircularRevealChangeHandler;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
import com.bluelinelabs.conductor.demo.BundleBuilder;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.changehandler.ArcFadeMoveChangeHandlerCompat;
import com.bluelinelabs.conductor.demo.changehandler.FlipChangeHandler;
@@ -4,7 +4,7 @@ import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.View;
import com.bluelinelabs.conductor.demo.App;
import com.bluelinelabs.conductor.demo.DemoApplication;
public abstract class RefWatchingController extends ButterKnifeController {
@@ -18,16 +18,16 @@ public abstract class RefWatchingController extends ButterKnifeController {
super.onDetach(view);
if (isDestroyed()) {
App.refWatcher.watch(view);
DemoApplication.refWatcher.watch(view);
}
}
@Override
public void onDestroy() {
if (getView() != null) {
App.refWatcher.watch(getView());
DemoApplication.refWatcher.watch(getView());
}
App.refWatcher.watch(this);
DemoApplication.refWatcher.watch(this);
}
}
@@ -1,4 +1,4 @@
package com.bluelinelabs.conductor.demo;
package com.bluelinelabs.conductor.demo.util;
import android.os.Bundle;
import android.os.Parcelable;
+5 -2
View File
@@ -5,8 +5,8 @@ ext {
buildToolsVersion = '23.0.2'
versionCode = 1
versionName = '1.0.2'
publishedVersionName = '1.0.2'
versionName = '1.0.3'
publishedVersionName = '1.0.3'
supportV4 = 'com.android.support:support-v4:23.1.1'
supportDesign = 'com.android.support:design:23.1.1'
@@ -24,4 +24,7 @@ ext {
junit = 'junit:junit:4.11'
roboelectric = 'org.robolectric:robolectric:3.0'
lintapi = 'com.android.tools.lint:lint-api:24.5.0'
lintchecks = 'com.android.tools.lint:lint-checks:24.5.0'
}
+5 -1
View File
@@ -1 +1,5 @@
include ':conductor', ':conductor-support', ':conductor-rxlifecycle', ':demo'
include ':conductor'
include':conductor-support'
include':conductor-rxlifecycle'
include':conductor-lint'
include':demo'