Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fff9f76e8a | |||
| bcc8f47856 | |||
| 61d68a9c6c | |||
| 5e7258973e | |||
| 194696e9cb | |||
| 5130909efc | |||
| 7874c4f72d | |||
| 8399868a63 | |||
| 3e97e7f8a8 | |||
| d5e54f33a0 | |||
| 58d1f37db0 | |||
| 7c175e4ce5 | |||
| 328b8a0873 | |||
| db446279bb | |||
| 80042ea71d | |||
| bb6b1c2089 | |||
| f13e7f83b8 | |||
| 633f70f9fb | |||
| 435674075c | |||
| c8f5bb5d4f | |||
| 52f44bb669 | |||
| 7412b5e96d | |||
| a9379d067e |
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -20,33 +20,24 @@ Conductor is architecture-agnostic and does not try to force any design decision
|
||||
## Installation
|
||||
|
||||
```gradle
|
||||
compile 'com.bluelinelabs:conductor:1.0.1'
|
||||
compile 'com.bluelinelabs:conductor:1.0.5'
|
||||
|
||||
// If you want the components that go along with
|
||||
// Android's support libraries (currently just a PagerAdapter):
|
||||
compile 'com.bluelinelabs:conductor-support:1.0.1'
|
||||
compile 'com.bluelinelabs:conductor-support:1.0.5'
|
||||
|
||||
// If you want RxJava/RxAndroid lifecycle support:
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle:1.0.1'
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle:1.0.5'
|
||||
```
|
||||
|
||||
## Components to Know
|
||||
|
||||
### Controller
|
||||
|
||||
The Controller is the View wrapper that will give you all of your lifecycle management features. Think of it as a lighter-weight and more predictable Fragment alternative with an easier to manage lifecycle.
|
||||
|
||||
### Router
|
||||
|
||||
The Router is responsible for handling navigation and the backstack. Controllers are pushed and popped in order to display and remove them.
|
||||
|
||||
### ControllerChangeHandler
|
||||
|
||||
ControllerChangeHandlers are responsible for performing the logic associated with pushing or popping Controllers. The most common implementation of these will be to animate between Controllers.
|
||||
|
||||
### ControllerTransaction
|
||||
|
||||
Transactions are used to define data about adding Controllers. RouterControllerTransactions are used to push a Controller to a Router with specified ControllerChangeHandlers, while ChildControllerTransactions are used to add child Controllers.
|
||||
| Conductor Components
|
||||
------|------------------------------
|
||||
__Controller__ | The Controller is the View wrapper that will give you all of your lifecycle management features. Think of it as a lighter-weight and more predictable Fragment alternative with an easier to manage lifecycle.
|
||||
__Router__ | A Router implements navigation and backstack handling for Controllers. Router objects are attached to Activity/containing ViewGroup pairs. Routers do not directly render or push Views to the container ViewGroup, but instead defer this responsibility to the ControllerChangeHandler specified in a given transaction.
|
||||
__ControllerChangeHandler__ | ControllerChangeHandlers are responsible for swapping the View for one Controller to the View of another. They can be useful for performing animations and transitions between Controllers. Several default ControllerChangeHandlers are included.
|
||||
__ControllerTransaction__ | Transactions are used to define data about adding Controllers. RouterControllerTransactions are used to push a Controller to a Router with specified ControllerChangeHandlers, while ChildControllerTransactions are used to add child Controllers.
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -87,8 +78,8 @@ public class MainActivity extends Activity {
|
||||
public class HomeController extends Controller {
|
||||
|
||||
@Override
|
||||
protected int layoutId() {
|
||||
return R.layout.controller_overlay;
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_home, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,6 +96,12 @@ public class HomeController extends Controller {
|
||||
|
||||
[Demo app](https://github.com/bluelinelabs/conductor/tree/master/demo) - Shows how to use all basic and most advanced functions of Conductor.
|
||||
|
||||
### Controller Lifecycle
|
||||
|
||||
The lifecycle of a Controller is significantly simpler to understand than that of a Fragment. A lifecycle diagram is shown below:
|
||||
|
||||

|
||||
|
||||
## Advanced Topics
|
||||
|
||||
### Retain View Modes
|
||||
|
||||
@@ -13,6 +13,7 @@ task javadoc(type: Javadoc) {
|
||||
source = android.sourceSets.main.java.srcDirs
|
||||
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
|
||||
exclude '**/R.java'
|
||||
exclude '**/internal/**'
|
||||
failOnError = false
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ plugins {
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
+100
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+108
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,14 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'de.mobilej.unmock:UnMockPlugin:0.3.6'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'de.mobilej.unmock'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
@@ -21,11 +31,37 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
lintChecks
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCompile rootProject.ext.junit
|
||||
testCompile rootProject.ext.roboelectric
|
||||
|
||||
compile rootProject.ext.supportAnnotations
|
||||
|
||||
lintChecks project(path: ':conductor-lint', configuration: 'lintChecks')
|
||||
}
|
||||
|
||||
unMock {
|
||||
downloadFrom 'https://oss.sonatype.org/content/groups/public/org/robolectric/android-all/4.3_r2-robolectric-0/android-all-4.3_r2-robolectric-0.jar'
|
||||
|
||||
keep "android.os.Bundle"
|
||||
keep "android.os.BaseBundle"
|
||||
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'
|
||||
|
||||
@@ -67,10 +67,12 @@ class Backstack implements Iterable<RouterTransaction> {
|
||||
mBackStack.push(transaction);
|
||||
}
|
||||
|
||||
public void popAll() {
|
||||
public List<RouterTransaction> popAll() {
|
||||
List<RouterTransaction> list = new ArrayList<>();
|
||||
while (!isEmpty()) {
|
||||
pop();
|
||||
list.add(pop());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public void saveInstanceState(Bundle outState) {
|
||||
|
||||
@@ -43,11 +43,12 @@ public class ChangeHandlerFrameLayout extends FrameLayout implements ControllerC
|
||||
|
||||
@Override
|
||||
public void onChangeStarted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler) {
|
||||
mInProgressTransactionCount++;
|
||||
mInProgressTransactionCount++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChangeCompleted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler) {
|
||||
mInProgressTransactionCount--;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerTransaction.ControllerChangeType;
|
||||
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
|
||||
import com.bluelinelabs.conductor.util.ClassUtils;
|
||||
import com.bluelinelabs.conductor.internal.ClassUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
@@ -50,6 +50,7 @@ public abstract class Controller {
|
||||
private final Bundle mArgs;
|
||||
|
||||
private Bundle mViewState;
|
||||
private boolean mIsBeingDestroyed;
|
||||
private boolean mDestroyed;
|
||||
private boolean mAttached;
|
||||
private Router mRouter;
|
||||
@@ -168,6 +169,13 @@ public abstract class Controller {
|
||||
return mDestroyed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this Controller is currently in the process of being destroyed.
|
||||
*/
|
||||
public final boolean isBeingDestroyed() {
|
||||
return mIsBeingDestroyed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this Controller is currently attached to a host View.
|
||||
*/
|
||||
@@ -193,14 +201,16 @@ public abstract class Controller {
|
||||
* Returns the Resources from the host Activity
|
||||
*/
|
||||
public final Resources getResources() {
|
||||
return getActivity().getResources();
|
||||
Activity activity = getActivity();
|
||||
return activity != null ? activity.getResources() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Application Context derived from the host Activity
|
||||
*/
|
||||
public final Context getApplicationContext() {
|
||||
return getActivity().getApplicationContext();
|
||||
Activity activity = getActivity();
|
||||
return activity != null ? activity.getApplicationContext() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -533,6 +543,10 @@ public abstract class Controller {
|
||||
mOverriddenPopHandler = overriddenPopHandler;
|
||||
}
|
||||
|
||||
final void prepareForConfigurationChange() {
|
||||
mNeedsAttach = mNeedsAttach || mAttached;
|
||||
}
|
||||
|
||||
final boolean getNeedsAttach() {
|
||||
return mNeedsAttach;
|
||||
}
|
||||
@@ -614,7 +628,7 @@ public abstract class Controller {
|
||||
if (isChangingConfigurations) {
|
||||
removeViewReference();
|
||||
} else {
|
||||
destroy();
|
||||
destroy(true);
|
||||
}
|
||||
|
||||
for (ChildControllerTransaction child : mChildControllers) {
|
||||
@@ -629,7 +643,6 @@ public abstract class Controller {
|
||||
|
||||
mAttached = true;
|
||||
mNeedsAttach = false;
|
||||
mView = view;
|
||||
|
||||
for (ChildControllerTransaction child : mChildControllers) {
|
||||
attachChildController(child, new SimpleSwapChangeHandler());
|
||||
@@ -660,7 +673,7 @@ public abstract class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
if (mRetainViewMode == RetainViewMode.RELEASE_DETACH || mDestroyed) {
|
||||
if (mRetainViewMode == RetainViewMode.RELEASE_DETACH || mIsBeingDestroyed) {
|
||||
removeViewReference();
|
||||
}
|
||||
|
||||
@@ -684,6 +697,10 @@ public abstract class Controller {
|
||||
lifecycleListener.postUnbindView(this);
|
||||
}
|
||||
}
|
||||
|
||||
if (mIsBeingDestroyed) {
|
||||
performDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
final View inflate(@NonNull ViewGroup parent) {
|
||||
@@ -697,20 +714,37 @@ public abstract class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
final void destroy() {
|
||||
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
|
||||
lifecycleListener.preDestroy(this);
|
||||
}
|
||||
final void performDestroy() {
|
||||
if (!mDestroyed) {
|
||||
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
|
||||
lifecycleListener.preDestroy(this);
|
||||
}
|
||||
|
||||
mDestroyed = true;
|
||||
onDestroy();
|
||||
mDestroyed = true;
|
||||
|
||||
onDestroy();
|
||||
|
||||
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
|
||||
lifecycleListener.postDestroy(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final void destroy() {
|
||||
destroy(false);
|
||||
}
|
||||
|
||||
final void destroy(boolean removeViews) {
|
||||
mIsBeingDestroyed = true;
|
||||
|
||||
for (ChildControllerTransaction child : mChildControllers) {
|
||||
child.controller.destroy();
|
||||
child.controller.destroy(removeViews);
|
||||
}
|
||||
|
||||
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
|
||||
lifecycleListener.postDestroy(this);
|
||||
if (!mAttached) {
|
||||
removeViewReference();
|
||||
} else if (removeViews) {
|
||||
detach(mView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,6 +861,8 @@ public abstract class Controller {
|
||||
lifecycleListener.preBindView(this, view);
|
||||
}
|
||||
|
||||
mView = view;
|
||||
|
||||
onBindView(view);
|
||||
|
||||
view.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
|
||||
|
||||
@@ -8,7 +8,7 @@ import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerTransaction.ControllerChangeType;
|
||||
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
|
||||
import com.bluelinelabs.conductor.util.ClassUtils;
|
||||
import com.bluelinelabs.conductor.internal.ClassUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller.LifecycleListener;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener;
|
||||
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
|
||||
import com.bluelinelabs.conductor.internal.LifecycleHandler;
|
||||
@@ -26,12 +27,13 @@ public class Router {
|
||||
private LifecycleHandler mLifecycleHandler;
|
||||
private ViewGroup mContainer;
|
||||
private final List<ControllerChangeListener> mChangeListeners = new ArrayList<>();
|
||||
private final List<Controller> mDestroyingControllers = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Returns this Router's host Activity
|
||||
*/
|
||||
public Activity getActivity() {
|
||||
return mLifecycleHandler.getLifecycleActivity();
|
||||
return mLifecycleHandler != null ? mLifecycleHandler.getLifecycleActivity() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +104,7 @@ public class Router {
|
||||
boolean poppingTopController = topController.controller == controller;
|
||||
|
||||
if (poppingTopController) {
|
||||
mBackStack.pop();
|
||||
trackDestroyingController(mBackStack.pop());
|
||||
} else {
|
||||
for (RouterTransaction transaction : mBackStack) {
|
||||
if (transaction.controller == controller) {
|
||||
@@ -140,7 +142,7 @@ public class Router {
|
||||
public void replaceTopController(@NonNull RouterTransaction transaction) {
|
||||
RouterTransaction topTransaction = mBackStack.peek();
|
||||
if (!mBackStack.isEmpty()) {
|
||||
mBackStack.pop();
|
||||
trackDestroyingController(mBackStack.pop());
|
||||
}
|
||||
|
||||
pushToBackstack(transaction);
|
||||
@@ -199,12 +201,32 @@ public class Router {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the root {@link Controller}. If any {@link Controller} are currently in the backstack, they will be removed.
|
||||
* Sets the root {@link Controller}. If any {@link Controller}s are currently in the backstack, they will be removed.
|
||||
*
|
||||
* @param controller The new root {@link Controller}
|
||||
*/
|
||||
public void setRoot(@NonNull Controller controller) {
|
||||
setRoot(controller, null);
|
||||
setRoot(controller, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the root {@link Controller}. If any {@link Controller}s are currently in the backstack, they will be removed.
|
||||
*
|
||||
* @param controller The new root {@link Controller}
|
||||
* @param tag The tag to use for this {@link Controller}
|
||||
*/
|
||||
public void setRoot(@NonNull Controller controller, String tag) {
|
||||
setRoot(controller, tag, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the root {@link Controller}. If any {@link Controller}s are currently in the backstack, they will be removed.
|
||||
*
|
||||
* @param controller The new root {@link Controller}
|
||||
* @param changeHandler The {@link ControllerChangeHandler} to use for setting the root
|
||||
*/
|
||||
public void setRoot(@NonNull Controller controller, ControllerChangeHandler changeHandler) {
|
||||
setRoot(controller, null, changeHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,14 +234,15 @@ public class Router {
|
||||
*
|
||||
* @param controller The new root {@link Controller}
|
||||
* @param tag The tag to use for this {@link Controller}
|
||||
* @param changeHandler The {@link ControllerChangeHandler} to use for setting the root
|
||||
*/
|
||||
public void setRoot(@NonNull Controller controller, String tag) {
|
||||
public void setRoot(@NonNull Controller controller, String tag, ControllerChangeHandler changeHandler) {
|
||||
mContainer.removeAllViews();
|
||||
mBackStack.popAll();
|
||||
trackDestroyingControllers(mBackStack.popAll());
|
||||
|
||||
RouterTransaction transaction = RouterTransaction.builder(controller)
|
||||
.tag(tag)
|
||||
.pushChangeHandler(new SimpleSwapChangeHandler())
|
||||
.pushChangeHandler(changeHandler != null ? changeHandler : new SimpleSwapChangeHandler())
|
||||
.popChangeHandler(new SimpleSwapChangeHandler())
|
||||
.build();
|
||||
|
||||
@@ -335,18 +358,29 @@ public class Router {
|
||||
}
|
||||
|
||||
public final void onActivitySaveInstanceState(Activity activity, Bundle outState) {
|
||||
if (activity.isChangingConfigurations()) {
|
||||
for (RouterTransaction transaction : mBackStack) {
|
||||
transaction.controller.prepareForConfigurationChange();
|
||||
}
|
||||
}
|
||||
|
||||
mBackStack.saveInstanceState(outState);
|
||||
}
|
||||
|
||||
public final void onActivityDestroyed(Activity activity) {
|
||||
mContainer.setOnHierarchyChangeListener(null);
|
||||
mLifecycleHandler = null;
|
||||
mContainer = null;
|
||||
mChangeListeners.clear();
|
||||
|
||||
for (RouterTransaction transaction : mBackStack) {
|
||||
transaction.controller.activityDestroyed(activity.isChangingConfigurations());
|
||||
}
|
||||
|
||||
for (Controller controller : mDestroyingControllers) {
|
||||
controller.activityDestroyed(activity.isChangingConfigurations());
|
||||
}
|
||||
|
||||
mLifecycleHandler = null;
|
||||
mContainer = null;
|
||||
}
|
||||
|
||||
public final void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
@@ -356,6 +390,7 @@ public class Router {
|
||||
private void popToTransaction(@NonNull RouterTransaction transaction, ControllerChangeHandler changeHandler) {
|
||||
RouterTransaction topTransaction = mBackStack.peek();
|
||||
List<RouterTransaction> poppedTransactions = mBackStack.popTo(transaction);
|
||||
trackDestroyingControllers(poppedTransactions);
|
||||
|
||||
if (poppedTransactions.size() > 0) {
|
||||
if (changeHandler == null) {
|
||||
@@ -430,4 +465,21 @@ public class Router {
|
||||
mBackStack.push(entry);
|
||||
}
|
||||
|
||||
private void trackDestroyingController(RouterTransaction transaction) {
|
||||
mDestroyingControllers.add(transaction.controller);
|
||||
|
||||
transaction.controller.addLifecycleListener(new LifecycleListener() {
|
||||
@Override
|
||||
public void postDestroy(@NonNull Controller controller) {
|
||||
mDestroyingControllers.remove(controller);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void trackDestroyingControllers(List<RouterTransaction> transactions) {
|
||||
for (RouterTransaction transaction : transactions) {
|
||||
trackDestroyingController(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+13
@@ -54,6 +54,14 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
|
||||
mRemovesFromViewOnPush = bundle.getBoolean(KEY_REMOVES_FROM_ON_PUSH);
|
||||
}
|
||||
|
||||
public long getAnimationDuration() {
|
||||
return mAnimationDuration;
|
||||
}
|
||||
|
||||
public boolean removesFromViewOnPush() {
|
||||
return mRemovesFromViewOnPush;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be overridden to return the Animator to use while replacing Views.
|
||||
*
|
||||
@@ -111,6 +119,11 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
|
||||
}
|
||||
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
changeListener.onChangeCompleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (from != null && (!isPush || mRemovesFromViewOnPush)) {
|
||||
|
||||
+2
-12
@@ -3,7 +3,6 @@ package com.bluelinelabs.conductor.changehandler;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.View;
|
||||
import android.view.View.OnAttachStateChangeListener;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
@@ -44,19 +43,10 @@ public class SimpleSwapChangeHandler extends ControllerChangeHandler {
|
||||
}
|
||||
|
||||
if (to != null && to.getParent() == null) {
|
||||
to.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View view) {
|
||||
view.removeOnAttachStateChangeListener(this);
|
||||
changeListener.onChangeCompleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View v) { }
|
||||
});
|
||||
|
||||
container.addView(to);
|
||||
}
|
||||
|
||||
changeListener.onChangeCompleted();
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -7,7 +7,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
import com.bluelinelabs.conductor.util.ClassUtils;
|
||||
import com.bluelinelabs.conductor.internal.ClassUtils;
|
||||
|
||||
/**
|
||||
* A base {@link ControllerChangeHandler} that facilitates using {@link android.transition.Transition}s to replace Controller Views.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bluelinelabs.conductor.util;
|
||||
package com.bluelinelabs.conductor.internal;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.bluelinelabs.conductor.util;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@TargetApi(VERSION_CODES.LOLLIPOP)
|
||||
public class ViewUtils {
|
||||
|
||||
public static View findViewWithTransitionName(@NonNull String name, @NonNull View view) {
|
||||
if (name.equals(view.getTransitionName())) {
|
||||
return view;
|
||||
}
|
||||
|
||||
if (view instanceof ViewGroup) {
|
||||
View namedView = findViewWithTransitionNameInGroup(name, (ViewGroup)view);
|
||||
|
||||
if (namedView != null) {
|
||||
return namedView;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static View findViewWithTransitionNameInGroup(@NonNull String name, @NonNull ViewGroup viewGroup) {
|
||||
for (int i = 0; i < viewGroup.getChildCount(); i++) {
|
||||
View namedView = findViewWithTransitionName(name, viewGroup.getChildAt(i));
|
||||
if (namedView != null) {
|
||||
return namedView;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ControllerChangeHandlerTests {
|
||||
|
||||
@Test
|
||||
public void testSaveRestore() {
|
||||
HorizontalChangeHandler horizontalChangeHandler = new HorizontalChangeHandler();
|
||||
FadeChangeHandler fadeChangeHandler = new FadeChangeHandler(120, false);
|
||||
|
||||
RouterTransaction transaction = RouterTransaction.builder(new TestController())
|
||||
.pushChangeHandler(horizontalChangeHandler)
|
||||
.popChangeHandler(fadeChangeHandler)
|
||||
.build();
|
||||
RouterTransaction restoredTransaction = new RouterTransaction(transaction.toBundle());
|
||||
|
||||
ControllerChangeHandler restoredHorizontal = restoredTransaction.getPushControllerChangeHandler();
|
||||
ControllerChangeHandler restoredFade = restoredTransaction.getPopControllerChangeHandler();
|
||||
|
||||
Assert.assertEquals(horizontalChangeHandler.getClass(), restoredHorizontal.getClass());
|
||||
Assert.assertEquals(fadeChangeHandler.getClass(), restoredFade.getClass());
|
||||
|
||||
HorizontalChangeHandler restoredHorizontalCast = (HorizontalChangeHandler)restoredHorizontal;
|
||||
FadeChangeHandler restoredFadeCast = (FadeChangeHandler)restoredFade;
|
||||
|
||||
Assert.assertEquals(horizontalChangeHandler.getAnimationDuration(), restoredHorizontalCast.getAnimationDuration());
|
||||
Assert.assertEquals(horizontalChangeHandler.removesFromViewOnPush(), restoredHorizontalCast.removesFromViewOnPush());
|
||||
|
||||
Assert.assertEquals(fadeChangeHandler.getAnimationDuration(), restoredFadeCast.getAnimationDuration());
|
||||
Assert.assertEquals(fadeChangeHandler.removesFromViewOnPush(), restoredFadeCast.removesFromViewOnPush());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller.LifecycleListener;
|
||||
import com.bluelinelabs.conductor.Controller.RetainViewMode;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeCompletedListener;
|
||||
import com.bluelinelabs.conductor.ControllerTransaction.ControllerChangeType;
|
||||
|
||||
@@ -66,6 +67,8 @@ public class ControllerTests {
|
||||
|
||||
mRouter.popCurrentController();
|
||||
|
||||
Assert.assertNull(controller.getView());
|
||||
|
||||
assertCalls(2, 2, 1, 1, 1, 1, 1);
|
||||
}
|
||||
|
||||
@@ -92,7 +95,7 @@ public class ControllerTests {
|
||||
|
||||
mActivityController.destroy();
|
||||
|
||||
assertCalls(1, 1, 1, 1, 0, 0, 1);
|
||||
assertCalls(1, 1, 1, 1, 1, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -168,6 +171,34 @@ public class ControllerTests {
|
||||
assertCalls(1, 1, 1, 1, 1, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testViewRetention() {
|
||||
Controller controller = new TestController();
|
||||
|
||||
// Test View getting released w/ RELEASE_DETACH
|
||||
controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH);
|
||||
Assert.assertNull(controller.getView());
|
||||
View view = controller.inflate(new FrameLayout(mRouter.getActivity()));
|
||||
Assert.assertNotNull(controller.getView());
|
||||
ViewUtils.setAttached(view, true);
|
||||
Assert.assertNotNull(controller.getView());
|
||||
ViewUtils.setAttached(view, false);
|
||||
Assert.assertNull(controller.getView());
|
||||
|
||||
// Test View getting retained w/ RETAIN_DETACH
|
||||
controller.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
|
||||
view = controller.inflate(new FrameLayout(mRouter.getActivity()));
|
||||
Assert.assertNotNull(controller.getView());
|
||||
ViewUtils.setAttached(view, true);
|
||||
Assert.assertNotNull(controller.getView());
|
||||
ViewUtils.setAttached(view, false);
|
||||
Assert.assertNotNull(controller.getView());
|
||||
|
||||
// Ensure re-setting RELEASE_DETACH releases
|
||||
controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH);
|
||||
Assert.assertNull(controller.getView());
|
||||
}
|
||||
|
||||
private ChangeHandler getPushHandler(final int changeStart, final int changeEnd, final int bindView, final int attach, final int unbindView, final int detach, final int destroy) {
|
||||
return new ChangeHandler(new ChangeHandlerListener() {
|
||||
@Override
|
||||
@@ -185,7 +216,7 @@ public class ControllerTests {
|
||||
return new ChangeHandler(new ChangeHandlerListener() {
|
||||
@Override
|
||||
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
|
||||
assertCalls(changeStart + 1, changeEnd, bindView, attach, unbindView, detach, destroy + 1);
|
||||
assertCalls(changeStart + 1, changeEnd, bindView, attach, unbindView, detach, destroy);
|
||||
container.removeView(from);
|
||||
ViewUtils.setAttached(from, false);
|
||||
assertCalls(changeStart + 1, changeEnd, bindView, attach, unbindView + 1, detach + 1, destroy + 1);
|
||||
@@ -247,7 +278,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;
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.IdRes;
|
||||
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
|
||||
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ControllerTransactionTests {
|
||||
|
||||
@Test
|
||||
public void testRouterSaveRestore() {
|
||||
RouterTransaction transaction = RouterTransaction.builder(new TestController())
|
||||
.pushChangeHandler(new HorizontalChangeHandler())
|
||||
.popChangeHandler(new VerticalChangeHandler())
|
||||
.tag("Test Tag")
|
||||
.build();
|
||||
|
||||
Bundle bundle = transaction.toBundle();
|
||||
|
||||
RouterTransaction restoredTransaction = new RouterTransaction(bundle);
|
||||
|
||||
Assert.assertEquals(transaction.getController().getClass(), restoredTransaction.getController().getClass());
|
||||
Assert.assertEquals(transaction.getPushControllerChangeHandler().getClass(), restoredTransaction.getPushControllerChangeHandler().getClass());
|
||||
Assert.assertEquals(transaction.getPopControllerChangeHandler().getClass(), restoredTransaction.getPopControllerChangeHandler().getClass());
|
||||
Assert.assertEquals(transaction.getTag(), restoredTransaction.getTag());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildSaveRestore() {
|
||||
@IdRes int layoutId = 234;
|
||||
ChildControllerTransaction transaction = ChildControllerTransaction.builder(new TestController(), layoutId)
|
||||
.pushChangeHandler(new HorizontalChangeHandler())
|
||||
.popChangeHandler(new VerticalChangeHandler())
|
||||
.tag("Test Tag")
|
||||
.build();
|
||||
|
||||
Bundle bundle = transaction.toBundle();
|
||||
|
||||
ChildControllerTransaction restoredTransaction = new ChildControllerTransaction(bundle);
|
||||
|
||||
Assert.assertEquals(transaction.containerId, restoredTransaction.containerId);
|
||||
Assert.assertEquals(transaction.getController().getClass(), restoredTransaction.getController().getClass());
|
||||
Assert.assertEquals(transaction.getPushControllerChangeHandler().getClass(), restoredTransaction.getPushControllerChangeHandler().getClass());
|
||||
Assert.assertEquals(transaction.getPopControllerChangeHandler().getClass(), restoredTransaction.getPopControllerChangeHandler().getClass());
|
||||
Assert.assertEquals(transaction.getTag(), restoredTransaction.getTag());
|
||||
}
|
||||
}
|
||||
+8
-5
@@ -1,6 +1,6 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
||||
@@ -41,11 +41,14 @@ dependencies {
|
||||
|
||||
compile rootProject.ext.butterknife
|
||||
|
||||
compile 'com.bluelinelabs:conductor:' + rootProject.ext.publishedVersionName
|
||||
compile 'com.bluelinelabs:conductor-support:' + rootProject.ext.publishedVersionName
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle:' + rootProject.ext.publishedVersionName
|
||||
|
||||
// compile project(':conductor-support')
|
||||
// compile project(':conductor-rxlifecycle')
|
||||
|
||||
debugCompile rootProject.ext.leakCanary
|
||||
releaseCompile rootProject.ext.leakCanaryNoOp
|
||||
testCompile rootProject.ext.leakCanaryNoOp
|
||||
|
||||
compile project(':conductor-support')
|
||||
compile project(':conductor-rxlifecycle')
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.bluelinelabs.conductor.demo"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
@@ -7,11 +8,13 @@
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:name="com.bluelinelabs.conductor.demo.App"
|
||||
android:name="com.bluelinelabs.conductor.demo.DemoApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
android:theme="@style/AppTheme"
|
||||
android:fullBackupContent="true"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
<activity
|
||||
android:name="com.bluelinelabs.conductor.demo.MainActivity"
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+31
-22
@@ -14,8 +14,6 @@ import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
|
||||
import com.bluelinelabs.conductor.demo.util.ColorUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ParentController extends RefWatchingController {
|
||||
|
||||
private static final int NUMBER_OF_CHILDREN = 5;
|
||||
@@ -37,15 +35,38 @@ public class ParentController extends RefWatchingController {
|
||||
}
|
||||
|
||||
private void addChild(final int index) {
|
||||
int frameId = getResources().getIdentifier("child_content_" + (index + 1), "id", getActivity().getPackageName());
|
||||
String tag = Integer.toString(index);
|
||||
|
||||
ChildController childController = new ChildController("Child Controller #" + index, ColorUtil.getMaterialColor(getResources(), index), false);
|
||||
addChildController(ChildControllerTransaction.builder(childController, frameId)
|
||||
.pushChangeHandler(new FadeChangeHandler())
|
||||
.popChangeHandler(new FadeChangeHandler())
|
||||
.build());
|
||||
if (getChildController(tag) == null) {
|
||||
int frameId = getResources().getIdentifier("child_content_" + (index + 1), "id", getActivity().getPackageName());
|
||||
|
||||
childController.addLifecycleListener(new LifecycleListener() {
|
||||
ChildController childController = new ChildController("Child Controller #" + index, ColorUtil.getMaterialColor(getResources(), index), false);
|
||||
addChildController(ChildControllerTransaction.builder(childController, frameId)
|
||||
.pushChangeHandler(new FadeChangeHandler())
|
||||
.popChangeHandler(new FadeChangeHandler())
|
||||
.tag(tag)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void removeChild(int index) {
|
||||
removeChildController(getChildControllers().get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleBack() {
|
||||
if (getChildControllers().size() == NUMBER_OF_CHILDREN && !mFinishing) {
|
||||
mFinishing = true;
|
||||
removeChild(getChildControllers().size() - 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChildController(ChildControllerTransaction transaction) {
|
||||
final int index = Integer.parseInt(transaction.tag);
|
||||
|
||||
transaction.controller.addLifecycleListener(new LifecycleListener() {
|
||||
@Override
|
||||
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
if (changeType == ControllerChangeType.PUSH_ENTER && index < NUMBER_OF_CHILDREN - 1) {
|
||||
@@ -59,19 +80,7 @@ public class ParentController extends RefWatchingController {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void removeChild(int index) {
|
||||
List<Controller> childControllers = getChildControllers();
|
||||
removeChildController(childControllers.get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleBack() {
|
||||
if (getChildControllers().size() == NUMBER_OF_CHILDREN && !mFinishing) {
|
||||
mFinishing = true;
|
||||
removeChild(getChildControllers().size() - 1);
|
||||
}
|
||||
return true;
|
||||
super.addChildController(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
+9
-9
@@ -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;
|
||||
@@ -58,7 +58,7 @@ public class TransitionDemoController extends RefWatchingController {
|
||||
|
||||
@Bind(R.id.tv_title) TextView mTvTitle;
|
||||
@Bind(R.id.btn_next) FloatingActionButton mBtnNext;
|
||||
View mContainerView;
|
||||
@Bind(R.id.transition_root) View mContainerView;
|
||||
|
||||
private TransitionDemo mTransitionDemo;
|
||||
|
||||
@@ -73,6 +73,12 @@ public class TransitionDemoController extends RefWatchingController {
|
||||
mTransitionDemo = TransitionDemo.fromIndex(args.getInt(KEY_INDEX));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(mTransitionDemo.layoutId, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(@NonNull View view) {
|
||||
super.onBindView(view);
|
||||
@@ -92,16 +98,9 @@ public class TransitionDemoController extends RefWatchingController {
|
||||
}
|
||||
|
||||
mBtnNext.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(getActivity(), buttonColor)));
|
||||
mContainerView = view;
|
||||
mTvTitle.setText(mTransitionDemo.title);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(mTransitionDemo.layoutId, container, false);
|
||||
}
|
||||
|
||||
@OnClick(R.id.btn_next) void onNextClicked() {
|
||||
final int nextIndex = mTransitionDemo.ordinal() + 1;
|
||||
|
||||
@@ -143,4 +142,5 @@ public class TransitionDemoController extends RefWatchingController {
|
||||
.popChangeHandler(changeHandler)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-4
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
package com.bluelinelabs.conductor.demo;
|
||||
package com.bluelinelabs.conductor.demo.util;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
+6
-2
@@ -5,7 +5,8 @@ ext {
|
||||
buildToolsVersion = '23.0.2'
|
||||
|
||||
versionCode = 1
|
||||
versionName = '1.0.1'
|
||||
versionName = '1.0.5'
|
||||
publishedVersionName = '1.0.5'
|
||||
|
||||
supportV4 = 'com.android.support:support-v4:23.1.1'
|
||||
supportDesign = 'com.android.support:design:23.1.1'
|
||||
@@ -23,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'
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
+5
-1
@@ -1 +1,5 @@
|
||||
include ':conductor', ':conductor-support', ':conductor-rxlifecycle', ':demo'
|
||||
include ':conductor'
|
||||
include':conductor-support'
|
||||
include':conductor-rxlifecycle'
|
||||
include':conductor-lint'
|
||||
include':demo'
|
||||
Reference in New Issue
Block a user