Compare commits

...

66 Commits

Author SHA1 Message Date
Eric Kuck 6dfc5839cc Fixed maven central artifacts 2016-05-12 10:36:54 -05:00
Eric Kuck 8cba63328f Travis should now ignore branches that don't need to be built yet [ci skip] 2016-05-11 17:12:55 -05:00
Eric Kuck 531cc3ff58 Version bump 2016-05-11 17:07:41 -05:00
Eric Kuck c7de32584b Fixed memory leak in view pager demo controller 2016-05-09 18:01:00 -05:00
Eric Kuck 625d1f15b6 Updated sonatype auth (#43) 2016-05-02 10:13:01 -05:00
Hannes Dorfmann 251fc42f67 Changed deploy_snaphsot permission (#41) 2016-04-29 09:58:25 -05:00
Hannes Dorfmann 0bf8e47c48 Fixing snapshot dependencies (#38)
* Resolving inner dependencies

* Resolving inner dependencies

* oops
2016-04-28 17:01:43 -05:00
Eric Kuck 7784f74102 Fixed artifact typo 2016-04-28 10:46:22 -05:00
Eric Kuck bfacab984b Added some tests for lifecycle call ordering 2016-04-27 15:45:59 -05:00
Eric Kuck f8a05731d9 Now removes the view's OnAttachStateListener before removing the reference
Fixes ordering of external lifecycle callbacks
2016-04-27 15:11:54 -05:00
Eric Kuck 116b5066c9 Fixed issue where view was not re-created if its old view was still attached. 2016-04-26 14:33:37 -05:00
Eric Kuck 011adca579 Fixed a typo 2016-04-25 13:33:36 -05:00
adi1133 95de69a006 Make getControllerWithInstanceId search recursively (#30)
* Make getControllerWithInstanceId search recursively

* Deprecate getChildControllerWithInstanceId()

* Rename getControllerWithInstanceId to findController making it internal
2016-04-25 13:11:12 -05:00
Eric Kuck 6cea976d10 Added info about snapshot builds to readme 2016-04-19 16:15:39 -05:00
Eric Kuck c573a8961c fixed conflict 2016-04-19 16:07:12 -05:00
Eric Kuck 6c444fecfb Added nexus details to travis.yml 2016-04-19 15:59:20 -05:00
Eric Kuck 29d1fd1c7d Switched to sonatype for deployment + snapshots 2016-04-19 15:52:40 -05:00
Eric Kuck f6493507f4 Options menu now works for child controllers
Added more tests
2016-04-18 14:07:52 -05:00
Eric Kuck b012df262d Dependency version update 2016-04-16 14:35:39 -05:00
Eric Kuck e028ed42da Version bump 2016-04-16 14:22:38 -05:00
Eric Kuck 43dc561ac2 Fixed up some save/restore view state stuff (incl. tests) 2016-04-16 13:51:37 -05:00
Eric Kuck 217b55090a Added test for backgrounding the host activity 2016-04-16 13:18:24 -05:00
Eric Kuck 5c8b78e41d Fixed orientation change test 2016-04-16 12:25:45 -05:00
Eric Kuck 899dd70d50 Merge pull request #25 from sockeqwe/viewStateListener
added methods to lifecyclelistener for saving/restoring viewstate
2016-04-16 12:04:51 -05:00
Eric Kuck d945571d31 Improved controller lifecycle test coverage (and clarity) 2016-04-16 12:04:20 -05:00
Hannes Dorfmann b7a4386d22 Renamed parameter of onRestoreViewState() to savedViewState 2016-04-16 14:01:41 +02:00
Hannes Dorfmann b117307340 added methods to lifecyclelistener for saving/restoring viewstate 2016-04-15 19:52:43 +02:00
Eric Kuck b8ccf3623f Fixed onRestoreState call ordering for controllers with parent/child relationships 2016-04-14 14:20:56 -05:00
Eric Kuck 495145b72b Router, Activity, target controller, etc is all now available in onRestoreInstanceState 2016-04-13 15:39:09 -05:00
Eric Kuck 99e25d65f2 Merge pull request #20 from adi1133/develop
Fix crash caused by recreating static nested classes using reflection
2016-04-12 16:41:05 -05:00
Adi Pascu 2619d13c8d Fix crash caused by recreating static nested classes using reflection 2016-04-12 23:49:10 +03:00
Eric Kuck 62a5a81107 Fixed typo 2016-04-12 14:33:03 -05:00
Eric Kuck d234dd4c75 Added external onActivityResult method to Controllers (for use with things like the Facebook SDK)
Removed CircularRevealChangeHandler from the core project
2016-04-12 14:18:01 -05:00
Eric Kuck a48b49cdbe All demos should now run on API < 21 2016-04-06 12:07:51 -05:00
Eric Kuck f0a488c711 Added options menu support for controllers
Updated demo app to have a toolbar
2016-04-05 13:34:14 -05:00
Eric Kuck 511c229364 Fixed issue with re-attaching when the host Activity never left with window 2016-04-04 23:23:01 -05:00
Eric Kuck b5d0e46740 Now enforces only setting a controller's target one time. 2016-04-04 17:45:29 -05:00
Eric Kuck 8f1be7fe21 Updated readme to reflect latest lifecycle updates 2016-04-04 17:29:22 -05:00
Eric Kuck 8a70c09fea Big lifecycle update + a few bug fixes 2016-04-04 17:03:43 -05:00
Eric Kuck 343e8c92e5 Merge pull request #9 from ravidsrk/patch-1
Added code syntax highlighting to Readme
2016-04-01 06:49:14 -05:00
Ravindra Kumar 332788ee01 Added code syntax highlighting to Readme 2016-04-01 00:51:53 -04:00
Eric Kuck acf77101a7 Merge pull request #4 from sockeqwe/patch-1
Conductor class final; added private Constructor
2016-03-31 18:29:21 -05:00
Hannes Dorfmann cba372966d Conductor class final; added private Constructor 2016-04-01 00:32:04 +02:00
Eric Kuck fff9f76e8a Version bump 2016-03-31 14:12:23 -05:00
Eric Kuck bcc8f47856 Fixed tests to work with corrected lifecycle 2016-03-30 18:35:52 -05:00
Eric Kuck 61d68a9c6c Tweaks to ensure lifecycle callback chain remains intact when the activity is destroyed unexpectedly 2016-03-30 18:32:05 -05:00
Eric Kuck 5e7258973e Tweaks to ensure lifecycle callback chain remains intact when the activity is destroyed unexpectedly 2016-03-30 18:30:23 -05:00
Eric Kuck 194696e9cb Added a method for animating setting the root controller 2016-03-30 12:43:13 -05:00
Eric Kuck 5130909efc Fixed readme's controller implementation 2016-03-28 22:30:43 -05:00
Eric Kuck 7874c4f72d Typo fix 2016-03-26 19:21:42 -05:00
Eric Kuck 8399868a63 Minor lifecycle bug fixes 2016-03-26 19:19:02 -05:00
Eric Kuck 3e97e7f8a8 Minor demo app cleanup 2016-03-23 09:27:36 -05:00
Eric Kuck d5e54f33a0 Fixed issue with screen rotation 2016-03-22 14:42:31 -05:00
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
Eric Kuck bb6b1c2089 Lifecycle diagram correction 2016-03-19 23:48:29 -05:00
Eric Kuck f13e7f83b8 Added license file 2016-03-19 15:19:51 -05:00
Eric Kuck 633f70f9fb Minor package reorg 2016-03-19 15:08:55 -05:00
Eric Kuck 435674075c Readme updae 2016-03-19 13:11:34 -05:00
Eric Kuck c8f5bb5d4f Fixed a typo 2016-03-18 16:23:26 -05:00
Eric Kuck 52f44bb669 Added lifecycle diagram 2016-03-18 16:21:39 -05:00
Eric Kuck 7412b5e96d Demo app now uses maven dependency instead of compiling the project directly in 2016-03-18 12:22:04 -05:00
Eric Kuck a9379d067e Added more tests + controller lifecycle tweak 2016-03-18 12:15:55 -05:00
82 changed files with 3066 additions and 794 deletions
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
#
# Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo.
#
# Adapted from https://coderwall.com/p/9b_lfq and
# http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/
SLUG="bluelinelabs/Conductor"
JDK="oraclejdk8"
BRANCH="develop"
set -e
if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then
echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'."
elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then
echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'."
elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
echo "Skipping snapshot deployment: was pull request."
elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then
echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'."
else
echo "Deploying snapshot..."
./gradlew clean uploadArchives
echo "Snapshot deployed!"
fi
-1
View File
@@ -15,7 +15,6 @@ gen-external-apklibs
# Gradle
.gradle
build
gradle.properties
# Maven
target
+26
View File
@@ -9,3 +9,29 @@ android:
script:
- ./gradlew test
jdk:
- oraclejdk8
branches:
except:
- v2
- feature/nested_controllers
- feature/save_restore_lifecycle
before_install:
- chmod +x .buildscript/deploy_snapshot.sh
after_success:
- .buildscript/deploy_snapshot.sh
cache:
directories:
- $HOME/.gradle
sudo: false
env:
global:
- secure: SoRbxmNTxnGY9qPR5Z8HraRLhHrq7eJ2UaHMuiXDSxpLwUI0IMw+8+l59PNdy/C5ZXXrr6jo6cj+sn/4u6VNg74e2h9i//O+kYGvbGJUnBx8uo1INOrVenpzSnIgxbRLxyN3ZDp8YgwJMl8MSCLM1nj2OMzjNRY5EBnEw5h3qNXBs4Hyhpp2FxVk7dA2yLMUZdOpFKJIsqhH1ZHnCEnYrLlx5cVM9yoefFmJ3PptgumtV8ciBnp0lgDGy5nTykPh6zJBz4rAXgOr95WHvoqpyBRAUZIUEgw/vB5aF8/g+CX2gvTlJYF2N9LgJTNHMEwd+zJtmjM8JzkuCfTT3uMDD3JK5O8eNU03a/+9AkbKpK2+Pt829ZPdkObavXi+oJykCmD5IirukVXE9ushR2J+fM4VOvJinsANSI0zjzFpjZMplX63lfhNu/4lj3AWV2G4rkZd3vZQU+4AuhGQ469RA9BFqUJDIsiQQJwHEAWIqo9WNi6H4H8OhferACd2T3d5Y0O3s0EG5JfdADBPh9YDIkB2zEtGc3gGdxFzxVmH48BJViubAHlH4SgJn7gn69T9wyKmJ1M8F9ph/CdhSHT3kADRDELPEEVXCcANG/verCbyxMlAMXvLNKIGgHD+A0/z9QS1WduOOZwWd1mAuNuEg/rq2OB8SoDTv/BseHrXOpc=
- secure: WpqrbdAvNUFn5cM/Iu3zJOaDvT3jWGHCRwvxQCzX9F8iJeTggB5dB2rjgUDCx8LJ8UAt0VCeOcGtR1RT3EHyaHorN3NWeLcBFAHSz2sXv+2xGkspsXwjfygghZTCdYEzhhvmWlz9Ln4s4QJ2fBFZA07pG0jw4Cp1hSQiJ1WlKfDQezldj8D3pPwg1oOq4b5+HVucQ6+PPVwzGk2c3etwb5205L8H8flRjZrP95mFa5n/H3b/HFIsKX5p+CPNIKCrjBEmX0nHXiV0+g6lBQBV1iCwT56vfmN8Urm4KLId71iMpmvstDxlBBRQx3sz41vxIWGFn/oN7iXJI6XfzVFkyvrd9XAQLQFffq4KpN0REy1L3rjO46sYRXu1ycCP5VFVAAwKZn+o1q6xRjCuma2Qj4tqY754pwPNyzXnndFLO7hoN8KjOgV2nk75+XlRG8LhP356CHET62QBZgJ+sl+aFM3hhknsaEuDQywo8Uz4WZL0lPmYqm5BImQT9sTEF6uQNofg4gMy/uqgGhpLtseQW3PoJXB6dmD5JdNxlOalkGSQ+aI/q5QvR6ruIiuap66o4Bu+YTvHiS2hVzmldvMmLFsU1/zECSI6Fs/vkwRN55R9mbPROWi8SzvftYk9shkFMC5QC1FXA/CHqX1W5nl/HpMrs8R9uPhdZ1lifCiW8Rk=
+202
View File
@@ -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.
+34 -35
View File
@@ -1,4 +1,4 @@
[![Travis Build](https://travis-ci.org/bluelinelabs/Conductor.svg)](https://travis-ci.org/bluelinelabs/Conductor)
[![Travis Build](https://travis-ci.org/bluelinelabs/Conductor.svg)](https://travis-ci.org/bluelinelabs/Conductor) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Conductor-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/3361)
# Conductor
@@ -14,45 +14,43 @@ A small, yet full-featured framework that allows building View-based Android app
:floppy_disk: | State persistence
:phone: | Callbacks for onActivityResult, onRequestPermissionsResult, etc
:european_post_office: | MVP / MVVM / VIPER / MVC ready
Conductor is architecture-agnostic and does not try to force any design decisions on the developer. We here at BlueLine Labs tend to use either MVP or MVVM, but it would work equally well with standard MVC or whatever else you want to throw at it.
## Installation
```gradle
compile 'com.bluelinelabs:conductor:1.0.1'
compile 'com.bluelinelabs:conductor:1.1.6'
// 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.1.6'
// If you want RxJava/RxAndroid lifecycle support:
compile 'com.bluelinelabs:conductor-rxlifecycle:1.0.1'
compile 'com.bluelinelabs:conductor-rxlifecycle:1.1.6'
```
SNAPSHOT:
```gradle
compile 'com.bluelinelabs:conductor:1.1.7-SNAPSHOT'
compile 'com.bluelinelabs:conductor-support:1.1.7-SNAPSHOT'
compile 'com.bluelinelabs:conductor-rxlifecycle:1.1.7-SNAPSHOT'
```
## 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
### Minimal Activity implementation
```
```java
public class MainActivity extends Activity {
private Router mRouter;
@@ -62,9 +60,9 @@ public class MainActivity extends Activity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewGroup container = (ViewGroup)findViewById(R.id.controller_container)
ViewGroup container = (ViewGroup)findViewById(R.id.controller_container)
mRouter = Conductor.attachRouter(this, container, savedInstanceState);
if (!mRouter.hasRootController()) {
mRouter.setRoot(new HomeController());
@@ -83,19 +81,14 @@ public class MainActivity extends Activity {
### Minimal Controller implementation
```
```java
public class HomeController extends Controller {
@Override
protected int layoutId() {
return R.layout.controller_overlay;
}
@Override
public void onBindView(@NonNull View view) {
super.onBindView(view);
((TextView)view.findViewById(R.id.tv_title)).setText("Hello World");
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
View view = inflater.inflate(R.layout.controller_home, container, false);
((TextView)view.findViewById(R.id.tv_title)).setText("Hello World");
return view;
}
}
@@ -105,6 +98,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:
![Controller Lifecycle](docs/Controller Lifecycle.jpg)
## Advanced Topics
### Retain View Modes
@@ -117,7 +116,7 @@ public class HomeController extends Controller {
`addChildController` can be called on a `Controller` in order to add nested `Controller`s. Child `Controller`s will receive all lifecycle callbacks that parents get.
### RxJava Lifecycle
If the RxLifecycle dependency has been added, there is an `RxController` available that can be used along with the standard [RxLifecycle library](https://github.com/trello/RxLifecycle). There is also a `ControllerLifecycleProvider` available if you do not wish to use this subclass.
If the RxLifecycle dependency has been added, there is an `RxController` available that can be used along with the standard [RxLifecycle library](https://github.com/trello/RxLifecycle). There is also a `ControllerLifecycleProvider` available if you do not wish to use this subclass.
## License
```
-100
View File
@@ -1,100 +0,0 @@
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'com.jfrog.bintray'
group = 'com.bluelinelabs'
version = rootProject.ext.versionName
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
task javadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
exclude '**/R.java'
failOnError = false
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
archives sourcesJar
}
if (project.hasProperty('pom_name')) {
install {
repositories.mavenInstaller {
pom.project {
name pom_name
description pom_description
url pom_url
packaging pom_packaging
groupId 'com.bluelinelabs'
artifactId project.hasProperty('artifactId') ? project.ext.artifactId : ''
organization {
name 'BlueLine Labs'
url 'http://bluelinelabs.com'
}
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
distribution 'repo'
}
}
scm {
url pom_url
connection pom_git_connection
developerConnection pom_git_connection
}
developers {
developer {
id 'erickuck'
name 'Eric Kuck'
}
}
}
}
}
bintray {
user = project.hasProperty('bintray_username') ? bintray_username : ''
key = project.hasProperty('bintray_api_key') ? bintray_api_key : ''
configurations = ['archives']
dryRun = false
publish = false
pkg {
repo = 'bluelinelabs'
userOrg = 'bluelinelabs'
name = pom_name
desc = pom_description
websiteUrl = pom_url
issueTrackerUrl = pom_issue_tracker_url
vcsUrl = pom_url
licenses = ['Apache-2.0']
labels = pom_labels
version {
name = project.version
gpg {
sign = true
passphrase = project.hasProperty('bintray_gpg_passphrase') ? bintray_gpg_passphrase : ''
}
mavenCentralSync {
sync = false
user = project.hasProperty('maven_central_username') ? maven_central_username : ''
password = project.hasProperty('maven_central_password') ? maven_central_password : ''
close = '1'
}
}
}
}
}
+2 -6
View File
@@ -3,18 +3,14 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
classpath 'com.android.tools.build:gradle:2.1.0'
}
}
plugins {
id "com.jfrog.bintray" version "1.5"
}
allprojects {
repositories {
jcenter()
mavenLocal()
}
}
+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);
}
}
+5 -4
View File
@@ -1,3 +1,6 @@
apply from: rootProject.file('dependencies.gradle')
apply from: rootProject.file('gradle-mvn-push.gradle')
apply plugin: 'com.android.library'
android {
@@ -16,8 +19,8 @@ android {
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
versionCode Integer.parseInt(project.VERSION_CODE)
versionName project.VERSION_NAME
}
}
@@ -31,5 +34,3 @@ dependencies {
ext.artifactId = 'conductor-rxlifecycle'
apply from: rootProject.file('dependencies.gradle')
apply from: rootProject.file('bll-gradle-push.gradle')
+3
View File
@@ -0,0 +1,3 @@
POM_NAME=Conductor RxLifecycle Extensions
POM_ARTIFACT_ID=conductor-rxlifecycle
POM_PACKAGING=aar
@@ -3,10 +3,10 @@ package com.bluelinelabs.conductor.rxlifecycle;
public enum ControllerEvent {
CREATE,
CREATE_VIEW,
ATTACH,
BIND_VIEW,
DETACH,
UNBIND_VIEW,
DESTROY_VIEW,
DESTROY
}
@@ -21,8 +21,8 @@ public class ControllerLifecycleSubjectHelper {
controller.addLifecycleListener(new LifecycleListener() {
@Override
public void preBindView(@NonNull Controller controller, @NonNull View view) {
subject.onNext(ControllerEvent.BIND_VIEW);
public void preCreateView(@NonNull Controller controller) {
subject.onNext(ControllerEvent.CREATE_VIEW);
}
@Override
@@ -31,13 +31,13 @@ public class ControllerLifecycleSubjectHelper {
}
@Override
public void preUnbindView(@NonNull Controller controller, @NonNull View view) {
subject.onNext(ControllerEvent.UNBIND_VIEW);
public void preDetach(@NonNull Controller controller, @NonNull View view) {
subject.onNext(ControllerEvent.DETACH);
}
@Override
public void preDetach(@NonNull Controller controller, @NonNull View view) {
subject.onNext(ControllerEvent.DETACH);
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
subject.onNext(ControllerEvent.DESTROY_VIEW);
}
@Override
@@ -3,6 +3,7 @@ package com.bluelinelabs.conductor.rxlifecycle;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import com.trello.rxlifecycle.LifecycleTransformer;
import com.trello.rxlifecycle.OutsideLifecycleException;
import com.trello.rxlifecycle.RxLifecycle;
@@ -20,7 +21,7 @@ public class RxControllerLifecycle {
*/
@NonNull
@CheckResult
public static <T> Observable.Transformer<T, T> bindController(@NonNull final Observable<ControllerEvent> lifecycle) {
public static <T> LifecycleTransformer<T> bindController(@NonNull final Observable<ControllerEvent> lifecycle) {
return RxLifecycle.bind(lifecycle, CONTROLLER_LIFECYCLE);
}
@@ -33,8 +34,8 @@ public class RxControllerLifecycle {
return ControllerEvent.DESTROY;
case ATTACH:
return ControllerEvent.DETACH;
case BIND_VIEW:
return ControllerEvent.UNBIND_VIEW;
case CREATE_VIEW:
return ControllerEvent.DESTROY_VIEW;
case DETACH:
return ControllerEvent.DESTROY;
default:
+5 -4
View File
@@ -1,3 +1,6 @@
apply from: rootProject.file('dependencies.gradle')
apply from: rootProject.file('gradle-mvn-push.gradle')
apply plugin: 'com.android.library'
android {
@@ -16,8 +19,8 @@ android {
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
versionCode Integer.parseInt(project.VERSION_CODE)
versionName project.VERSION_NAME
}
}
@@ -28,5 +31,3 @@ dependencies {
ext.artifactId = 'conductor-support'
apply from: rootProject.file('dependencies.gradle')
apply from: rootProject.file('bll-gradle-push.gradle')
+3
View File
@@ -0,0 +1,3 @@
POM_NAME=Conductor Support Extensions
POM_ARTIFACT_ID=conductor-support
POM_PACKAGING=aar
+39 -3
View File
@@ -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
@@ -16,19 +26,45 @@ android {
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
versionCode Integer.parseInt(project.VERSION_CODE)
versionName project.VERSION_NAME
}
}
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'
apply from: rootProject.file('dependencies.gradle')
apply from: rootProject.file('bll-gradle-push.gradle')
apply from: rootProject.file('gradle-mvn-push.gradle')
+3
View File
@@ -0,0 +1,3 @@
POM_NAME=Conductor
POM_ARTIFACT_ID=conductor
POM_PACKAGING=aar
@@ -67,16 +67,18 @@ 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) {
public void detachAndSaveInstanceState(Bundle outState) {
ArrayList<Bundle> entryBundles = new ArrayList<>(mBackStack.size());
for (RouterTransaction entry : mBackStack) {
entryBundles.add(entry.toBundle());
entryBundles.add(entry.detachAndSaveInstanceState());
}
outState.putParcelableArrayList(KEY_ENTRIES, entryBundles);
@@ -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--;
}
}
@@ -31,8 +31,8 @@ public class ChildControllerTransaction extends ControllerTransaction {
}
@Override
public Bundle toBundle() {
Bundle bundle = super.toBundle();
public Bundle detachAndSaveInstanceState() {
Bundle bundle = super.detachAndSaveInstanceState();
bundle.putInt(KEY_CONTAINER_ID, containerId);
bundle.putBoolean(KEY_ADD_TO_LOCAL_BACKSTACK, addToLocalBackstack);
return bundle;
@@ -10,8 +10,10 @@ import com.bluelinelabs.conductor.internal.LifecycleHandler;
/**
* Point of initial interaction with Conductor. Used to attach a {@link Router} to your Activity.
*/
public class Conductor {
public final class Conductor {
private Conductor() {}
/**
* Conductor will create a {@link Router} that has been initialized for your Activity and containing ViewGroup.
* If an existing {@link Router} is already associated with this Activity/ViewGroup pair, either in memory
@@ -11,13 +11,17 @@ import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
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 com.bluelinelabs.conductor.internal.RouterRequiringFunc;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
@@ -41,7 +45,7 @@ public abstract class Controller {
private static final String KEY_TARGET_INSTANCE_ID = "Controller.target.instanceId";
private static final String KEY_ARGS = "Controller.args";
private static final String KEY_NEEDS_ATTACH = "Controller.needsAttach";
private static final String KEY_REQUESTED_PERMISSIONS = "Controller.childControllers";
private static final String KEY_REQUESTED_PERMISSIONS = "Controller.requestedPermissions";
private static final String KEY_OVERRIDDEN_PUSH_HANDLER = "Controller.overriddenPushHandler";
private static final String KEY_OVERRIDDEN_POP_HANDLER = "Controller.overriddenPopHandler";
private static final String KEY_VIEW_STATE_HIERARCHY = "Controller.viewState.hierarchy";
@@ -50,20 +54,28 @@ public abstract class Controller {
private final Bundle mArgs;
private Bundle mViewState;
private Bundle mSavedInstanceState;
private boolean mIsBeingDestroyed;
private boolean mDestroyed;
private boolean mAttached;
private boolean mHasOptionsMenu;
private boolean mOptionsMenuHidden;
private boolean mViewIsAttached;
private Router mRouter;
private View mView;
private Controller mParentController;
private String mInstanceId;
private String mTargetInstanceId;
private boolean mNeedsAttach;
private boolean mHasSavedViewState;
private ControllerChangeHandler mOverriddenPushHandler;
private ControllerChangeHandler mOverriddenPopHandler;
private RetainViewMode mRetainViewMode = RetainViewMode.RELEASE_DETACH;
private OnAttachStateChangeListener mOnAttachStateChangeListener;
private final List<ChildControllerTransaction> mChildControllers = new ArrayList<>();
private final List<LifecycleListener> mLifecycleListeners = new ArrayList<>();
private final ArrayList<String> mRequestedPermissions = new ArrayList<>();
private final ArrayList<RouterRequiringFunc> mOnRouterSetListeners = new ArrayList<>();
static Controller newInstance(Bundle bundle) {
final String className = bundle.getString(KEY_CLASS_NAME);
@@ -107,15 +119,16 @@ public abstract class Controller {
/**
* Called when the controller is ready to display its view. A valid view must be returned. The standard body
* for this method will be {@code return inflater.inflate(R.layout.my_layout, container, false);}
* for this method will be {@code return inflater.inflate(R.layout.my_layout, container, false);}, plus
* any binding code.
*
* @param inflater The LayoutInflater that should be used to inflate views
* @param inflater The LayoutInflater that should be used to inflate views
* @param container The parent view that this Controller's view will eventually be attached to.
* This Controller's view should NOT be added in this method. It is simply passed in
* so that valid LayoutParams can be used during inflation.
*/
@NonNull
protected abstract View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container);
protected abstract View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container);
/**
* Returns the {@link Router} object that can be used for pushing or popping other Controllers
@@ -168,6 +181,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 +213,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;
}
/**
@@ -235,10 +257,11 @@ public abstract class Controller {
/**
* Returns the child Controller with the given instance id, if available.
*
* @param instanceId The instance ID being searched for
* @return The matching child Controller, if one exists
* @deprecated Use {@link #getChildController(String)} or {@link #getChildControllers()} instead.
*/
@Deprecated
public final Controller getChildControllerWithInstanceId(String instanceId) {
for (ControllerTransaction transaction : mChildControllers) {
if (transaction.controller.getInstanceId().equals(instanceId)) {
@@ -248,6 +271,24 @@ public abstract class Controller {
return null;
}
/**
* Returns the Controller with the given instance id, if available.
* May return the controller itself or a matching descendant
* @param instanceId The instance ID being searched for
* @return The matching Controller, if one exists
*/
final Controller findController(String instanceId) {
if (mInstanceId.equals(instanceId))
return this;
for (ControllerTransaction transaction : mChildControllers) {
Controller controllerWithId = transaction.controller.findController(instanceId);
if (controllerWithId != null)
return controllerWithId;
}
return null;
}
/**
* Returns all of this Controller's child Controllers
*/
@@ -261,22 +302,18 @@ public abstract class Controller {
/**
* Optional target for this Controller. One reason this could be used is to send results back to the Controller
* that started this one. Target Controllers are retained across instances.
*
* @param target The Controller that is the target of this one.
*/
public final void setTargetController(Controller target) {
mTargetInstanceId = target != null ? target.getInstanceId() : null;
onTargetControllerSet(target);
}
/**
* This method will be called when {@link #setTargetController(Controller)} is called. It is recommended
* that started this one. Target Controllers are retained across instances. It is recommended
* that Controllers enforce that their target Controller conform to a specific Interface.
*
* @param target The Controller that is the target of this one.
*/
public void onTargetControllerSet(Controller target) { }
public void setTargetController(Controller target) {
if (mTargetInstanceId != null) {
throw new RuntimeException("Target controller already set. A controller's target may only be set once.");
}
mTargetInstanceId = target != null ? target.getInstanceId() : null;
}
/**
* Returns the target Controller that was set with the {@link #setTargetController(Controller)} method
@@ -287,27 +324,19 @@ public abstract class Controller {
return mTargetInstanceId != null ? mRouter.getControllerWithInstanceId(mTargetInstanceId) : null;
}
/**
* Called when this Controller's View is inflated. This should overridden to bind the View
* to variables, either using findViewById or something like Butterknife.
*
* @param view The View to which this Controller should be bound.
*/
protected void onBindView(@NonNull final View view) { }
/**
* Called when this Controller's View is being destroyed. This should overridden to unbind the View
* from any local variables.
*
* @param view The View to which this Controller should be bound.
*/
protected void onUnbindView(View view) { }
protected void onDestroyView(View view) { }
/**
* Called when this Controller begins the process of being swapped in or out of the host view.
*
* @param changeHandler The {@link ControllerChangeHandler} that's managing the swap
* @param changeType The type of change that's occurring
* @param changeType The type of change that's occurring
*/
protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { }
@@ -315,7 +344,7 @@ public abstract class Controller {
* Called when this Controller completes the process of being swapped in or out of the host view.
*
* @param changeHandler The {@link ControllerChangeHandler} that's managing the swap
* @param changeType The type of change that occurred
* @param changeType The type of change that occurred
*/
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { }
@@ -363,7 +392,7 @@ public abstract class Controller {
* Controller lifecycle (ex: when another Controller has been pushed on top of it), care should be taken
* to save anything needed to reconstruct the View.
*
* @param view This Controller's View, passed for convenience
* @param view This Controller's View, passed for convenience
* @param outState The Bundle into which the View state should be saved
*/
protected void onSaveViewState(@NonNull View view, @NonNull Bundle outState) { }
@@ -372,7 +401,7 @@ public abstract class Controller {
* Restores data that was saved in the {@link #onSaveViewState(View, Bundle)} method. This should be overridden
* to restore the View's state to where it was before it was destroyed.
*
* @param view This Controller's View, passed for convenience
* @param view This Controller's View, passed for convenience
* @param savedViewState The bundle that has data to be restored
*/
protected void onRestoreViewState(@NonNull View view, @NonNull Bundle savedViewState) { }
@@ -392,18 +421,43 @@ public abstract class Controller {
*/
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { }
/**
* Calls startActivity(Intent) from this Controller's host Activity.
*/
public final void startActivity(final Intent intent) {
executeWithRouter(new RouterRequiringFunc() {
@Override public void execute() { mRouter.getLifecycleHandler().startActivity(intent); }
});
}
/**
* Calls startActivityForResult(Intent, int) from this Controller's host Activity.
*/
public final void startActivityForResult(Intent intent, int requestCode) {
getRouter().getLifecycleHandler().startActivityForResult(mInstanceId, intent, requestCode);
public final void startActivityForResult(final Intent intent, final int requestCode) {
executeWithRouter(new RouterRequiringFunc() {
@Override public void execute() { mRouter.getLifecycleHandler().startActivityForResult(mInstanceId, intent, requestCode); }
});
}
/**
* Calls startActivityForResult(Intent, int, Bundle) from this Controller's host Activity.
*/
public final void startActivityForResult(Intent intent, int requestCode, Bundle options) {
getRouter().getLifecycleHandler().startActivityForResult(mInstanceId, intent, requestCode, options);
public final void startActivityForResult(final Intent intent, final int requestCode, final Bundle options) {
executeWithRouter(new RouterRequiringFunc() {
@Override public void execute() { mRouter.getLifecycleHandler().startActivityForResult(mInstanceId, intent, requestCode, options); }
});
}
/**
* Registers this Controller to handle onActivityResult responses. Calling this method is NOT
* necessary when calling {@link #startActivityForResult(Intent, int)}
*
* @param requestCode The request code being registered for.
*/
public final void registerForActivityResult(final int requestCode) {
executeWithRouter(new RouterRequiringFunc() {
@Override public void execute() { mRouter.getLifecycleHandler().registerForActivityRequest(mInstanceId, requestCode); }
});
}
/**
@@ -411,8 +465,8 @@ public abstract class Controller {
* the result.
*
* @param requestCode The requestCode passed to startActivityForResult
* @param resultCode The resultCode that was returned to the host Activity's onActivityResult method
* @param data The data Intent that was returned to the host Activity's onActivityResult method
* @param resultCode The resultCode that was returned to the host Activity's onActivityResult method
* @param data The data Intent that was returned to the host Activity's onActivityResult method
*/
public void onActivityResult(int requestCode, int resultCode, Intent data) { }
@@ -422,9 +476,12 @@ public abstract class Controller {
* {@link #onRequestPermissionsResult(int, String[], int[])} will be forwarded back to this Controller by the system.
*/
@TargetApi(Build.VERSION_CODES.M)
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
public final void requestPermissions(@NonNull final String[] permissions, final int requestCode) {
mRequestedPermissions.addAll(Arrays.asList(permissions));
getRouter().getLifecycleHandler().requestPermissions(mInstanceId, permissions, requestCode);
executeWithRouter(new RouterRequiringFunc() {
@Override public void execute() { mRouter.getLifecycleHandler().requestPermissions(mInstanceId, permissions, requestCode); }
});
}
/**
@@ -440,8 +497,8 @@ public abstract class Controller {
/**
* Should be overridden if this Controller has requested runtime permissions and needs to handle the user's response.
*
* @param requestCode The requestCode that was used to request the permissions
* @param permissions The array of permissions requested
* @param requestCode The requestCode that was used to request the permissions
* @param permissions The array of permissions requested
* @param grantResults The results for each permission requested
*/
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { }
@@ -533,6 +590,70 @@ public abstract class Controller {
mOverriddenPopHandler = overriddenPopHandler;
}
/**
* Registers/unregisters for participation in populating the options menu by receiving options-related
* callbacks, such as {@link #onCreateOptionsMenu(Menu, MenuInflater)}
*
* @param hasOptionsMenu If true, this controller's options menu callbacks will be called.
*/
public final void setHasOptionsMenu(boolean hasOptionsMenu) {
boolean invalidate = mAttached && !mOptionsMenuHidden && mHasOptionsMenu != hasOptionsMenu;
mHasOptionsMenu = hasOptionsMenu;
if (invalidate) {
mRouter.invalidateOptionsMenu();
}
}
/**
* Sets whether or not this controller's menu items should be visible. This is useful for hiding the
* controller's options menu items when its UI is hidden, and not just when it is detached from the
* window (the default).
*
* @param optionsMenuHidden Defaults to false. If true, this controller's menu items will not be shown.
*/
public final void setOptionsMenuHidden(boolean optionsMenuHidden) {
boolean invalidate = mAttached && mHasOptionsMenu && mOptionsMenuHidden != optionsMenuHidden;
mOptionsMenuHidden = optionsMenuHidden;
if (invalidate) {
mRouter.invalidateOptionsMenu();
}
}
/**
* Adds option items to the host Activity's standard options menu. This will only be called if
* {@link #setHasOptionsMenu(boolean)} has been called.
*
* @param menu The menu into which your options should be placed.
* @param inflater The inflater that can be used to inflate your menu items.
*/
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { }
/**
* Prepare the screen's options menu to be displayed. This is called directly before showing the
* menu and can be used modify its contents.
*
* @param menu The menu that will be displayed
*/
public void onPrepareOptionsMenu(Menu menu) { }
/**
* Called when an option menu item has been selected by the user.
*
* @param item The selected item.
* @return True if this event has been consumed, false if it has not.
*/
public boolean onOptionsItemSelected(MenuItem item) {
return false;
}
final void prepareForActivityPause() {
mNeedsAttach = mNeedsAttach || mAttached;
}
final boolean getNeedsAttach() {
return mNeedsAttach;
}
@@ -547,10 +668,29 @@ public abstract class Controller {
}
final void setRouter(@NonNull Router router) {
mRouter = router;
if (mRouter != router) {
mRouter = router;
for (ChildControllerTransaction child : mChildControllers) {
child.controller.setRouter(router);
performOnRestoreInstanceState();
for (RouterRequiringFunc listener : mOnRouterSetListeners) {
listener.execute();
}
mOnRouterSetListeners.clear();
for (ChildControllerTransaction child : mChildControllers) {
child.controller.setRouter(router);
}
} else {
performOnRestoreInstanceState();
}
}
final void executeWithRouter(@NonNull RouterRequiringFunc listener) {
if (mRouter != null) {
listener.execute();
} else {
mOnRouterSetListeners.add(listener);
}
}
@@ -587,6 +727,10 @@ public abstract class Controller {
}
final void activityResumed(Activity activity) {
if (!mAttached && mView != null && mViewIsAttached) {
attach(mView);
}
onActivityResumed(activity);
for (ChildControllerTransaction child : mChildControllers) {
@@ -614,7 +758,7 @@ public abstract class Controller {
if (isChangingConfigurations) {
removeViewReference();
} else {
destroy();
destroy(true);
}
for (ChildControllerTransaction child : mChildControllers) {
@@ -623,13 +767,14 @@ public abstract class Controller {
}
private void attach(@NonNull View view) {
mHasSavedViewState = false;
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.preAttach(this, view);
}
mAttached = true;
mNeedsAttach = false;
mView = view;
for (ChildControllerTransaction child : mChildControllers) {
attachChildController(child, new SimpleSwapChangeHandler());
@@ -637,84 +782,155 @@ public abstract class Controller {
onAttach(view);
if (mHasOptionsMenu && !mOptionsMenuHidden) {
mRouter.invalidateOptionsMenu();
}
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.postAttach(this, view);
}
}
private void detach(@NonNull View view) {
private void detach(@NonNull View view, boolean allowViewRefRemoval) {
final boolean removeViewRef = allowViewRefRemoval && (mRetainViewMode == RetainViewMode.RELEASE_DETACH || mIsBeingDestroyed);
if (mAttached) {
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.preDetach(this, view);
}
saveViewState(view);
mAttached = false;
onDetach(view);
if (mHasOptionsMenu && !mOptionsMenuHidden) {
mRouter.invalidateOptionsMenu();
}
for (ChildControllerTransaction child : mChildControllers) {
ViewGroup container = (ViewGroup)mView.findViewById(child.containerId);
if (container != null) {
container.removeView(child.controller.getView());
}
}
if (mRetainViewMode == RetainViewMode.RELEASE_DETACH || mDestroyed) {
removeViewReference();
}
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.postDetach(this, view);
}
if (removeViewRef) {
removeViewReference();
}
} else if (removeViewRef) {
removeViewReference();
}
}
private void removeViewReference() {
if (mView != null) {
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.preUnbindView(this, mView);
if (!mIsBeingDestroyed && !mHasSavedViewState) {
saveViewState(mView);
}
onUnbindView(mView);
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.preDestroyView(this, mView);
}
onDestroyView(mView);
mView.removeOnAttachStateChangeListener(mOnAttachStateChangeListener);
mView = null;
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.postUnbindView(this);
lifecycleListener.postDestroyView(this);
}
}
if (mIsBeingDestroyed) {
performDestroy();
}
}
final View inflate(@NonNull ViewGroup parent) {
if (mView != null && mView.getParent() != null && mView.getParent() != parent) {
detach(mView, true);
removeViewReference();
}
if (mView == null) {
View view = inflateView(LayoutInflater.from(parent.getContext()), parent);
bindView(view);
restoreViewState(view);
return view;
} else {
return mView;
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.preCreateView(this);
}
mView = onCreateView(LayoutInflater.from(parent.getContext()), parent);
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.postCreateView(this, mView);
}
restoreViewState(mView);
mOnAttachStateChangeListener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
if (v == mView) {
mViewIsAttached = true;
}
attach(v);
}
@Override
public void onViewDetachedFromWindow(View v) {
mViewIsAttached = false;
detach(v, true);
}
};
mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
}
return mView;
}
final void performDestroy() {
if (!mDestroyed) {
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.preDestroy(this);
}
mDestroyed = true;
if (mRouter != null) {
mRouter.getLifecycleHandler().unregisterForActivityRequests(mInstanceId);
}
onDestroy();
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.postDestroy(this);
}
}
}
final void destroy() {
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.preDestroy(this);
}
destroy(false);
}
mDestroyed = true;
onDestroy();
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, true);
}
}
final void saveViewState(@NonNull View view) {
mHasSavedViewState = true;
mViewState = new Bundle();
SparseArray<Parcelable> hierarchyState = new SparseArray<>();
@@ -730,6 +946,10 @@ public abstract class Controller {
child.controller.saveViewState(child.controller.mView);
}
}
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.onSaveViewState(this, mViewState);
}
}
final void restoreViewState(@NonNull View view) {
@@ -742,16 +962,24 @@ public abstract class Controller {
child.controller.restoreViewState(child.controller.mView);
}
}
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.onRestoreViewState(this, mViewState);
}
}
}
final Bundle saveInstanceState() {
final Bundle detachAndSaveInstanceState() {
if (mAttached && mView != null) {
detach(mView, mIsBeingDestroyed);
}
if (!mHasSavedViewState && mView != null) {
saveViewState(mView);
}
Bundle outState = new Bundle();
outState.putString(KEY_CLASS_NAME, getClass().getCanonicalName());
outState.putString(KEY_CLASS_NAME, getClass().getName());
outState.putBundle(KEY_VIEW_STATE, mViewState);
outState.putBundle(KEY_ARGS, mArgs);
outState.putString(KEY_INSTANCE_ID, mInstanceId);
@@ -768,7 +996,7 @@ public abstract class Controller {
ArrayList<Bundle> childBundles = new ArrayList<>();
for (ChildControllerTransaction childController : mChildControllers) {
childBundles.add(childController.toBundle());
childBundles.add(childController.detachAndSaveInstanceState());
}
outState.putParcelableArrayList(KEY_CHILDREN, childBundles);
@@ -798,11 +1026,19 @@ public abstract class Controller {
addChildController(new ChildControllerTransaction(childBundle));
}
Bundle savedState = savedInstanceState.getBundle(KEY_SAVED_STATE);
onRestoreInstanceState(savedState);
mSavedInstanceState = savedInstanceState.getBundle(KEY_SAVED_STATE);
performOnRestoreInstanceState();
}
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.onRestoreInstanceState(this, savedState);
private void performOnRestoreInstanceState() {
if (mSavedInstanceState != null && mRouter != null) {
onRestoreInstanceState(mSavedInstanceState);
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.onRestoreInstanceState(this, mSavedInstanceState);
}
mSavedInstanceState = null;
}
}
@@ -822,30 +1058,45 @@ public abstract class Controller {
}
}
private void bindView(@NonNull final View view) {
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.preBindView(this, view);
}
onBindView(view);
view.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
attach(view);
final void createOptionsMenu(Menu menu, MenuInflater inflater) {
if (mAttached) {
if (mHasOptionsMenu && !mOptionsMenuHidden) {
onCreateOptionsMenu(menu, inflater);
}
@Override
public void onViewDetachedFromWindow(View v) {
detach(view);
for (ChildControllerTransaction child : mChildControllers) {
child.controller.createOptionsMenu(menu, inflater);
}
});
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.postBindView(this, view);
}
}
final void prepareOptionsMenu(Menu menu) {
if (mAttached) {
if (mHasOptionsMenu && !mOptionsMenuHidden) {
onPrepareOptionsMenu(menu);
}
for (ChildControllerTransaction child : mChildControllers) {
child.controller.onPrepareOptionsMenu(menu);
}
}
}
final boolean optionsItemSelected(MenuItem item) {
if (mAttached) {
if (mHasOptionsMenu && !mOptionsMenuHidden && onOptionsItemSelected(item)) {
return true;
}
for (ChildControllerTransaction child : mChildControllers) {
if (child.controller.onOptionsItemSelected(item)) {
return true;
}
}
}
return false;
}
private void ensureRequiredConstructor() {
Constructor[] constructors = getClass().getConstructors();
if (getBundleConstructor(constructors) == null && getDefaultConstructor(constructors) == null) {
@@ -885,23 +1136,26 @@ public abstract class Controller {
public void onChangeStart(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { }
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { }
public void preBindView(@NonNull Controller controller, @NonNull View view) { }
public void postBindView(@NonNull Controller controller, @NonNull View view) { }
public void preCreateView(@NonNull Controller controller) { }
public void postCreateView(@NonNull Controller controller, @NonNull View view) { }
public void preAttach(@NonNull Controller controller, @NonNull View view) { }
public void postAttach(@NonNull Controller controller, @NonNull View view) { }
public void preUnbindView(@NonNull Controller controller, @NonNull View view) { }
public void postUnbindView(@NonNull Controller controller) { }
public void preDetach(@NonNull Controller controller, @NonNull View view) { }
public void postDetach(@NonNull Controller controller, @NonNull View view) { }
public void preDestroyView(@NonNull Controller controller, @NonNull View view) { }
public void postDestroyView(@NonNull Controller controller) { }
public void preDestroy(@NonNull Controller controller) { }
public void postDestroy(@NonNull Controller controller) { }
public void onSaveInstanceState(@NonNull Controller controller, @NonNull Bundle outState) { }
public void onRestoreInstanceState(@NonNull Controller controller, @NonNull Bundle savedInstanceState) { }
public void onSaveViewState(@NonNull Controller controller, @NonNull Bundle outState) { }
public void onRestoreViewState(@NonNull Controller controller, @NonNull Bundle savedViewState) { }
}
}
@@ -2,13 +2,12 @@ package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
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;
@@ -54,7 +53,7 @@ public abstract class ControllerChangeHandler {
final Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putString(KEY_CLASS_NAME, getClass().getCanonicalName());
bundle.putString(KEY_CLASS_NAME, getClass().getName());
Bundle savedState = new Bundle();
saveToBundle(savedState);
@@ -71,7 +70,7 @@ public abstract class ControllerChangeHandler {
}
}
public static ControllerChangeHandler fromBundle(@Nullable Bundle bundle) {
public static ControllerChangeHandler fromBundle(Bundle bundle) {
if (bundle != null) {
String className = bundle.getString(KEY_CLASS_NAME);
ControllerChangeHandler changeHandler = ClassUtils.newInstance(className);
@@ -13,16 +13,24 @@ public class ControllerTransaction {
*/
public enum ControllerChangeType {
/** The Controller is being pushed to the host container */
PUSH_ENTER,
PUSH_ENTER(true, true),
/** The Controller is being pushed to the backstack as another Controller is pushed to the host container */
PUSH_EXIT,
PUSH_EXIT(true, false),
/** The Controller is being popped from the backstack and placed in the host container as another Controller is popped */
POP_ENTER,
POP_ENTER(false, true),
/** The Controller is being popped from the host contianer */
POP_EXIT
/** The Controller is being popped from the host container */
POP_EXIT(false, false);
public boolean isPush;
public boolean isEnter;
ControllerChangeType(boolean isPush, boolean isEnter) {
this.isPush = isPush;
this.isEnter = isEnter;
}
}
private static final String KEY_VIEW_CONTROLLER_BUNDLE = "ControllerTransaction.controller.bundle";
@@ -77,10 +85,10 @@ public class ControllerTransaction {
/**
* Used to serialize this transaction into a Bundle
*/
public Bundle toBundle() {
public Bundle detachAndSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putBundle(KEY_VIEW_CONTROLLER_BUNDLE, controller.saveInstanceState());
bundle.putBundle(KEY_VIEW_CONTROLLER_BUNDLE, controller.detachAndSaveInstanceState());
if (mPushControllerChangeHandler != null) {
bundle.putBundle(KEY_PUSH_TRANSITION, mPushControllerChangeHandler.toBundle());
@@ -4,8 +4,13 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
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,28 +31,25 @@ 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;
}
/**
* This should be called by the host Activity when its onActivityResult method is called. The call will be forwarded
* to the {@link Controller} with the instanceId passed in.
* This should be called by the host Activity when its onActivityResult method is called if the instanceId
* of the controller that called startActivityForResult is not known.
*
* @param instanceId The instanceId of the Controller to which this result should be forwarded
* @param requestCode The Activity's onActivityResult requestCode
* @param resultCode The Activity's onActivityResult resultCode
* @param data The Activity's onActivityResult data
*/
public void onActivityResult(String instanceId, int requestCode, int resultCode, Intent data) {
Controller controller = getControllerWithInstanceId(instanceId);
if (controller != null) {
controller.onActivityResult(requestCode, resultCode, data);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
getLifecycleHandler().onActivityResult(requestCode, resultCode, data);
}
/**
@@ -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,19 +234,33 @@ 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) {
mContainer.removeAllViews();
mBackStack.popAll();
public void setRoot(@NonNull Controller controller, String tag, ControllerChangeHandler changeHandler) {
RouterTransaction currentTop = mBackStack.peek();
if (currentTop != null && currentTop.controller.getView() != null) {
final View fromView = currentTop.controller.getView();
final int childCount = mContainer.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = mContainer.getChildAt(i);
if (child != fromView) {
mContainer.removeView(child);
}
}
}
trackDestroyingControllers(mBackStack.popAll());
RouterTransaction transaction = RouterTransaction.builder(controller)
.tag(tag)
.pushChangeHandler(new SimpleSwapChangeHandler())
.pushChangeHandler(changeHandler != null ? changeHandler : new SimpleSwapChangeHandler())
.popChangeHandler(new SimpleSwapChangeHandler())
.build();
pushToBackstack(transaction);
performControllerChange(transaction, null, true);
performControllerChange(transaction, currentTop, true);
}
/**
@@ -235,14 +271,10 @@ public class Router {
*/
public Controller getControllerWithInstanceId(String instanceId) {
for (ControllerTransaction transaction : mBackStack) {
if (transaction.controller.getInstanceId().equals(instanceId)) {
return transaction.controller;
} else {
Controller childWithId = transaction.controller.getChildControllerWithInstanceId(instanceId);
if (childWithId != null) {
return childWithId;
Controller controllerWithId = transaction.controller.findController(instanceId);
if (controllerWithId != null) {
return controllerWithId;
}
}
}
return null;
}
@@ -310,6 +342,13 @@ public class Router {
}
}
public final void onActivityResult(String instanceId, int requestCode, int resultCode, Intent data) {
Controller controller = getControllerWithInstanceId(instanceId);
if (controller != null) {
controller.onActivityResult(requestCode, resultCode, data);
}
}
public final void onActivityStarted(Activity activity) {
for (RouterTransaction transaction : mBackStack) {
transaction.controller.activityStarted(activity);
@@ -335,27 +374,69 @@ public class Router {
}
public final void onActivitySaveInstanceState(Activity activity, Bundle outState) {
mBackStack.saveInstanceState(outState);
for (RouterTransaction transaction : mBackStack) {
transaction.controller.prepareForActivityPause();
}
mBackStack.detachAndSaveInstanceState(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) {
mBackStack.restoreInstanceState(savedInstanceState);
Iterator<RouterTransaction> backstackIterator = mBackStack.reverseIterator();
while (backstackIterator.hasNext()) {
backstackIterator.next().controller.setRouter(this);
}
}
public final void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
for (RouterTransaction transaction : mBackStack) {
transaction.controller.createOptionsMenu(menu, inflater);
}
}
public final void onPrepareOptionsMenu(Menu menu) {
for (RouterTransaction transaction : mBackStack) {
transaction.controller.prepareOptionsMenu(menu);
}
}
public final boolean onOptionsItemSelected(MenuItem item) {
for (RouterTransaction transaction : mBackStack) {
if (transaction.controller.optionsItemSelected(item)) {
return true;
}
}
return false;
}
final void invalidateOptionsMenu() {
if (mLifecycleHandler != null) {
mLifecycleHandler.getFragmentManager().invalidateOptionsMenu();
}
}
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 +511,23 @@ public class Router {
mBackStack.push(entry);
}
private void trackDestroyingController(RouterTransaction transaction) {
if (!transaction.controller.isDestroyed()) {
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);
}
}
}
@@ -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)) {
@@ -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();
}
}
@@ -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.
@@ -50,8 +50,8 @@ public class TransitionChangeHandlerCompat extends ControllerChangeHandler {
public void saveToBundle(@NonNull Bundle bundle) {
super.saveToBundle(bundle);
bundle.putString(KEY_TRANSITION_HANDLER_CLASS, mTransitionChangeHandler.getClass().getCanonicalName());
bundle.putString(KEY_FALLBACK_HANDLER_CLASS, mFallbackChangeHandler.getClass().getCanonicalName());
bundle.putString(KEY_TRANSITION_HANDLER_CLASS, mTransitionChangeHandler.getClass().getName());
bundle.putString(KEY_FALLBACK_HANDLER_CLASS, mFallbackChangeHandler.getClass().getName());
Bundle transitionBundle = new Bundle();
mTransitionChangeHandler.saveToBundle(transitionBundle);
@@ -1,4 +1,4 @@
package com.bluelinelabs.conductor.util;
package com.bluelinelabs.conductor.internal;
import android.text.TextUtils;
@@ -9,6 +9,9 @@ import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Router;
@@ -33,6 +36,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
public LifecycleHandler() {
setRetainInstance(true);
setHasOptionsMenu(true);
}
private static LifecycleHandler findInActivity(Activity activity) {
@@ -57,13 +61,16 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
Router router = mRouterMap.get(getRouterHashKey(container));
if (router == null) {
router = new Router();
router.setHost(this, container);
if (savedInstanceState != null) {
router.onRestoreInstanceState(savedInstanceState);
}
mRouterMap.put(getRouterHashKey(container), router);
} else {
router.setHost(this, container);
}
router.setHost(this, container);
return router;
}
@@ -155,13 +162,54 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
return super.shouldShowRequestPermissionRationale(permission);
}
public void startActivityForResult(String instanceId, Intent intent, int requestCode) {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
for (Router router : mRouterMap.values()) {
router.onCreateOptionsMenu(menu, inflater);
}
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
for (Router router : mRouterMap.values()) {
router.onPrepareOptionsMenu(menu);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
for (Router router : mRouterMap.values()) {
if (router.onOptionsItemSelected(item)) {
return true;
}
}
return super.onOptionsItemSelected(item);
}
public void registerForActivityRequest(String instanceId, int requestCode) {
mActivityRequestMap.put(requestCode, instanceId);
}
public void unregisterForActivityRequests(String instanceId) {
for (int i = mActivityRequestMap.size() - 1; i >= 0; i--) {
if (instanceId.equals(mActivityRequestMap.get(mActivityRequestMap.keyAt(i)))) {
mActivityRequestMap.removeAt(i);
}
}
}
public void startActivityForResult(String instanceId, Intent intent, int requestCode) {
registerForActivityRequest(instanceId, requestCode);
startActivityForResult(intent, requestCode);
}
public void startActivityForResult(String instanceId, Intent intent, int requestCode, Bundle options) {
mActivityRequestMap.put(requestCode, instanceId);
registerForActivityRequest(instanceId, requestCode);
startActivityForResult(intent, requestCode, options);
}
@@ -0,0 +1,5 @@
package com.bluelinelabs.conductor.internal;
public interface RouterRequiringFunc {
void execute();
}
@@ -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,175 @@
package com.bluelinelabs.conductor;
import android.os.Parcel;
import android.os.Parcelable;
public class CallState implements Parcelable {
public int changeStartCalls;
public int changeEndCalls;
public int createViewCalls;
public int attachCalls;
public int destroyViewCalls;
public int detachCalls;
public int destroyCalls;
public int saveInstanceStateCalls;
public int restoreInstanceStateCalls;
public int saveViewStateCalls;
public int restoreViewStateCalls;
public int onActivityResultCalls;
public int onRequestPermissionsResultCalls;
public int createOptionsMenuCalls;
public CallState() {
this(false);
}
public CallState(boolean setupForAddedController) {
if (setupForAddedController) {
changeStartCalls++;
changeEndCalls++;
createViewCalls++;
attachCalls++;
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CallState callState = (CallState)o;
if (changeStartCalls != callState.changeStartCalls) {
return false;
}
if (changeEndCalls != callState.changeEndCalls) {
return false;
}
if (createViewCalls != callState.createViewCalls) {
return false;
}
if (attachCalls != callState.attachCalls) {
return false;
}
if (destroyViewCalls != callState.destroyViewCalls) {
return false;
}
if (detachCalls != callState.detachCalls) {
return false;
}
if (destroyCalls != callState.destroyCalls) {
return false;
}
if (saveInstanceStateCalls != callState.saveInstanceStateCalls) {
return false;
}
if (saveViewStateCalls != callState.saveViewStateCalls) {
return false;
}
if (restoreViewStateCalls != callState.restoreViewStateCalls) {
return false;
}
if (onActivityResultCalls != callState.onActivityResultCalls) {
return false;
}
if (onRequestPermissionsResultCalls != callState.onRequestPermissionsResultCalls) {
return false;
}
if (createOptionsMenuCalls != callState.createOptionsMenuCalls) {
return false;
}
return restoreInstanceStateCalls == callState.restoreInstanceStateCalls;
}
@Override
public int hashCode() {
int result = changeStartCalls;
result = 31 * result + changeEndCalls;
result = 31 * result + createViewCalls;
result = 31 * result + attachCalls;
result = 31 * result + destroyViewCalls;
result = 31 * result + detachCalls;
result = 31 * result + destroyCalls;
result = 31 * result + saveInstanceStateCalls;
result = 31 * result + restoreInstanceStateCalls;
result = 31 * result + saveViewStateCalls;
result = 31 * result + restoreViewStateCalls;
result = 31 * result + onActivityResultCalls;
result = 31 * result + onRequestPermissionsResultCalls;
result = 31 * result + createOptionsMenuCalls;
return result;
}
@Override
public String toString() {
return "\nCallState{" +
"\n changeStartCalls=" + changeStartCalls +
"\n changeEndCalls=" + changeEndCalls +
"\n createViewCalls=" + createViewCalls +
"\n attachCalls=" + attachCalls +
"\n destroyViewCalls=" + destroyViewCalls +
"\n detachCalls=" + detachCalls +
"\n destroyCalls=" + destroyCalls +
"\n saveInstanceStateCalls=" + saveInstanceStateCalls +
"\n restoreInstanceStateCalls=" + restoreInstanceStateCalls +
"\n saveViewStateCalls=" + saveViewStateCalls +
"\n restoreViewStateCalls=" + restoreViewStateCalls +
"\n onActivityResultCalls= " + onActivityResultCalls +
"\n onRequestPermissionsResultCalls= " + onRequestPermissionsResultCalls +
"\n createOptionsMenuCalls= " + createOptionsMenuCalls +
"}\n";
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(changeStartCalls);
out.writeInt(changeEndCalls);
out.writeInt(createViewCalls);
out.writeInt(attachCalls);
out.writeInt(destroyViewCalls);
out.writeInt(detachCalls);
out.writeInt(destroyCalls);
out.writeInt(saveInstanceStateCalls);
out.writeInt(restoreInstanceStateCalls);
out.writeInt(saveViewStateCalls);
out.writeInt(restoreViewStateCalls);
out.writeInt(onActivityResultCalls);
out.writeInt(onRequestPermissionsResultCalls);
out.writeInt(createOptionsMenuCalls);
}
public static final Parcelable.Creator<CallState> CREATOR = new Parcelable.Creator<CallState>() {
public CallState createFromParcel(Parcel in) {
CallState state = new CallState();
state.changeStartCalls = in.readInt();
state.changeEndCalls = in.readInt();
state.createViewCalls = in.readInt();
state.attachCalls = in.readInt();
state.destroyViewCalls = in.readInt();
state.detachCalls = in.readInt();
state.destroyCalls = in.readInt();
state.saveInstanceStateCalls = in.readInt();
state.restoreInstanceStateCalls = in.readInt();
state.saveViewStateCalls = in.readInt();
state.restoreViewStateCalls = in.readInt();
state.onActivityResultCalls = in.readInt();
state.onRequestPermissionsResultCalls = in.readInt();
state.createOptionsMenuCalls = in.readInt();
return state;
}
public CallState[] newArray(int size) {
return new CallState[size];
}
};
}
@@ -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.detachAndSaveInstanceState());
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());
}
}
@@ -0,0 +1,627 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.bluelinelabs.conductor.Controller.LifecycleListener;
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeCompletedListener;
import com.bluelinelabs.conductor.ControllerTransaction.ControllerChangeType;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ControllerLifecycleTests {
private ActivityController<TestActivity> mActivityController;
private Router mRouter;
private CallState mCurrentCallState;
public void createActivityController(Bundle savedInstanceState) {
mActivityController = Robolectric.buildActivity(TestActivity.class).create(savedInstanceState).start();
@IdRes int containerId = 4;
FrameLayout routerContainer = new FrameLayout(mActivityController.get());
routerContainer.setId(containerId);
mRouter = Conductor.attachRouter(mActivityController.get(), routerContainer, savedInstanceState);
if (!mRouter.hasRootController()) {
mRouter.setRoot(new TestController());
}
}
@Before
public void setup() {
createActivityController(null);
mCurrentCallState = new CallState();
}
@Test
public void testNormalLifecycle() {
TestController controller = new TestController();
attachLifecycleListener(controller);
CallState expectedCallState = new CallState();
assertCalls(expectedCallState, controller);
mRouter.pushController(RouterTransaction.builder(controller)
.pushChangeHandler(getPushHandler(expectedCallState, controller))
.popChangeHandler(getPopHandler(expectedCallState, controller))
.build()
);
assertCalls(expectedCallState, controller);
mRouter.popCurrentController();
Assert.assertNull(controller.getView());
assertCalls(expectedCallState, controller);
}
@Test
public void testLifecycleWithActivityDestroy() {
TestController controller = new TestController();
attachLifecycleListener(controller);
CallState expectedCallState = new CallState();
assertCalls(expectedCallState, controller);
mRouter.pushController(RouterTransaction.builder(controller)
.pushChangeHandler(getPushHandler(expectedCallState, controller))
.build()
);
assertCalls(expectedCallState, controller);
mActivityController.pause();
assertCalls(expectedCallState, controller);
mActivityController.stop();
assertCalls(expectedCallState, controller);
mActivityController.destroy();
expectedCallState.detachCalls++;
expectedCallState.destroyViewCalls++;
expectedCallState.destroyCalls++;
assertCalls(expectedCallState, controller);
}
@Test
public void testLifecycleWithActivityConfigurationChange() {
TestController controller = new TestController();
attachLifecycleListener(controller);
CallState expectedCallState = new CallState();
assertCalls(expectedCallState, controller);
mRouter.pushController(RouterTransaction.builder(controller)
.pushChangeHandler(getPushHandler(expectedCallState, controller))
.tag("root")
.build()
);
assertCalls(expectedCallState, controller);
mActivityController.get().isChangingConfigurations = true;
Bundle bundle = new Bundle();
mActivityController.saveInstanceState(bundle);
expectedCallState.detachCalls++;
expectedCallState.saveViewStateCalls++;
expectedCallState.saveInstanceStateCalls++;
assertCalls(expectedCallState, controller);
mActivityController.pause();
assertCalls(expectedCallState, controller);
mActivityController.stop();
assertCalls(expectedCallState, controller);
mActivityController.destroy();
expectedCallState.destroyViewCalls++;
assertCalls(expectedCallState, controller);
createActivityController(bundle);
controller = (TestController)mRouter.getControllerWithTag("root");
expectedCallState.restoreInstanceStateCalls++;
expectedCallState.restoreViewStateCalls++;
expectedCallState.changeStartCalls++;
expectedCallState.changeEndCalls++;
expectedCallState.createViewCalls++;
// Lifecycle listener isn't attached during restore, grab the current views from the controller for this stuff...
mCurrentCallState.restoreInstanceStateCalls = controller.currentCallState.restoreInstanceStateCalls;
mCurrentCallState.restoreViewStateCalls = controller.currentCallState.restoreViewStateCalls;
mCurrentCallState.changeStartCalls = controller.currentCallState.changeStartCalls;
mCurrentCallState.changeEndCalls = controller.currentCallState.changeEndCalls;
mCurrentCallState.createViewCalls = controller.currentCallState.createViewCalls;
assertCalls(expectedCallState, controller);
mActivityController.resume();
assertCalls(expectedCallState, controller);
}
@Test
public void testLifecycleWithActivityBackground() {
TestController controller = new TestController();
attachLifecycleListener(controller);
CallState expectedCallState = new CallState();
assertCalls(expectedCallState, controller);
mRouter.pushController(RouterTransaction.builder(controller)
.pushChangeHandler(getPushHandler(expectedCallState, controller))
.build()
);
assertCalls(expectedCallState, controller);
mActivityController.pause();
Bundle bundle = new Bundle();
mActivityController.saveInstanceState(bundle);
expectedCallState.detachCalls++;
expectedCallState.saveInstanceStateCalls++;
expectedCallState.saveViewStateCalls++;
assertCalls(expectedCallState, controller);
mActivityController.resume();
expectedCallState.createViewCalls++;
expectedCallState.restoreViewStateCalls++;
}
@Test
public void testLifecycleCallOrder() {
final TestController testController = new TestController();
final CallState callState = new CallState();
testController.addLifecycleListener(new LifecycleListener() {
@Override
public void preCreateView(@NonNull Controller controller) {
callState.createViewCalls++;
Assert.assertEquals(1, callState.createViewCalls);
Assert.assertEquals(0, testController.currentCallState.createViewCalls);
Assert.assertEquals(0, callState.attachCalls);
Assert.assertEquals(0, testController.currentCallState.attachCalls);
Assert.assertEquals(0, callState.detachCalls);
Assert.assertEquals(0, testController.currentCallState.detachCalls);
Assert.assertEquals(0, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
callState.createViewCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(0, callState.attachCalls);
Assert.assertEquals(0, testController.currentCallState.attachCalls);
Assert.assertEquals(0, callState.detachCalls);
Assert.assertEquals(0, testController.currentCallState.detachCalls);
Assert.assertEquals(0, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void preAttach(@NonNull Controller controller, @NonNull View view) {
callState.attachCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(1, callState.attachCalls);
Assert.assertEquals(0, testController.currentCallState.attachCalls);
Assert.assertEquals(0, callState.detachCalls);
Assert.assertEquals(0, testController.currentCallState.detachCalls);
Assert.assertEquals(0, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postAttach(@NonNull Controller controller, @NonNull View view) {
callState.attachCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(0, callState.detachCalls);
Assert.assertEquals(0, testController.currentCallState.detachCalls);
Assert.assertEquals(0, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void preDetach(@NonNull Controller controller, @NonNull View view) {
callState.detachCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(1, callState.detachCalls);
Assert.assertEquals(0, testController.currentCallState.detachCalls);
Assert.assertEquals(0, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postDetach(@NonNull Controller controller, @NonNull View view) {
callState.detachCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(2, callState.detachCalls);
Assert.assertEquals(1, testController.currentCallState.detachCalls);
Assert.assertEquals(0, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
callState.destroyViewCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(2, callState.detachCalls);
Assert.assertEquals(1, testController.currentCallState.detachCalls);
Assert.assertEquals(1, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postDestroyView(@NonNull Controller controller) {
callState.destroyViewCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(2, callState.detachCalls);
Assert.assertEquals(1, testController.currentCallState.detachCalls);
Assert.assertEquals(2, callState.destroyViewCalls);
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void preDestroy(@NonNull Controller controller) {
callState.destroyCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(2, callState.detachCalls);
Assert.assertEquals(1, testController.currentCallState.detachCalls);
Assert.assertEquals(2, callState.destroyViewCalls);
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(1, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postDestroy(@NonNull Controller controller) {
callState.destroyCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(2, callState.detachCalls);
Assert.assertEquals(1, testController.currentCallState.detachCalls);
Assert.assertEquals(2, callState.destroyViewCalls);
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(2, callState.destroyCalls);
Assert.assertEquals(1, testController.currentCallState.destroyCalls);
}
});
mRouter.pushController(RouterTransaction.builder(testController)
.pushChangeHandler(new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
container.addView(to);
ViewUtils.setAttached(to, true);
changeListener.onChangeCompleted();
}
}))
.popChangeHandler(new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
container.removeView(from);
ViewUtils.setAttached(from, false);
changeListener.onChangeCompleted();
}
}))
.build());
mRouter.popController(testController);
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(2, callState.detachCalls);
Assert.assertEquals(2, callState.destroyViewCalls);
Assert.assertEquals(2, callState.destroyCalls);
}
@Test
public void testChildLifecycle() {
Controller parent = new TestController();
mRouter.pushController(RouterTransaction.builder(parent)
.pushChangeHandler(new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
container.addView(to);
ViewUtils.setAttached(to, true);
changeListener.onChangeCompleted();
}
}))
.build());
TestController child = new TestController();
attachLifecycleListener(child);
CallState expectedCallState = new CallState();
assertCalls(expectedCallState, child);
parent.addChildController(ChildControllerTransaction.builder(child, TestController.VIEW_ID)
.pushChangeHandler(getPushHandler(expectedCallState, child))
.popChangeHandler(getPopHandler(expectedCallState, child))
.build()
);
assertCalls(expectedCallState, child);
parent.removeChildController(child);
assertCalls(expectedCallState, child);
}
@Test
public void testChildLifecycle2() {
Controller parent = new TestController();
mRouter.pushController(RouterTransaction.builder(parent)
.pushChangeHandler(new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
container.addView(to);
ViewUtils.setAttached(to, true);
changeListener.onChangeCompleted();
}
}))
.popChangeHandler(new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
container.removeView(from);
ViewUtils.setAttached(from, false);
changeListener.onChangeCompleted();
}
}))
.build());
TestController child = new TestController();
attachLifecycleListener(child);
CallState expectedCallState = new CallState();
assertCalls(expectedCallState, child);
parent.addChildController(ChildControllerTransaction.builder(child, TestController.VIEW_ID)
.pushChangeHandler(getPushHandler(expectedCallState, child))
.popChangeHandler(getPopHandler(expectedCallState, child))
.build()
);
assertCalls(expectedCallState, child);
mRouter.popCurrentController();
ViewUtils.setAttached(child.getView(), false);
expectedCallState.detachCalls++;
expectedCallState.destroyViewCalls++;
expectedCallState.destroyCalls++;
assertCalls(expectedCallState, child);
}
private ChangeHandler getPushHandler(final CallState expectedCallState, final TestController controller) {
return new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
expectedCallState.changeStartCalls++;
expectedCallState.createViewCalls++;
assertCalls(expectedCallState, controller);
container.addView(to);
ViewUtils.setAttached(to, true);
expectedCallState.attachCalls++;
assertCalls(expectedCallState, controller);
changeListener.onChangeCompleted();
expectedCallState.changeEndCalls++;
assertCalls(expectedCallState, controller);
}
});
}
private ChangeHandler getPopHandler(final CallState expectedCallState, final TestController controller) {
return new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
expectedCallState.changeStartCalls++;
assertCalls(expectedCallState, controller);
container.removeView(from);
ViewUtils.setAttached(from, false);
expectedCallState.destroyViewCalls++;
expectedCallState.detachCalls++;
expectedCallState.destroyCalls++;
assertCalls(expectedCallState, controller);
changeListener.onChangeCompleted();
expectedCallState.changeEndCalls++;
assertCalls(expectedCallState, controller);
}
});
}
private void assertCalls(CallState callState, TestController controller) {
Assert.assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
Assert.assertEquals("Expected call counts and lifecycle call counts do not match.", callState, mCurrentCallState);
}
private void attachLifecycleListener(Controller controller) {
controller.addLifecycleListener(new LifecycleListener() {
@Override
public void onChangeStart(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
mCurrentCallState.changeStartCalls++;
}
@Override
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
mCurrentCallState.changeEndCalls++;
}
@Override
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
mCurrentCallState.createViewCalls++;
}
@Override
public void postAttach(@NonNull Controller controller, @NonNull View view) {
mCurrentCallState.attachCalls++;
}
@Override
public void postDestroyView(@NonNull Controller controller) {
mCurrentCallState.destroyViewCalls++;
}
@Override
public void postDetach(@NonNull Controller controller, @NonNull View view) {
mCurrentCallState.detachCalls++;
}
@Override
public void postDestroy(@NonNull Controller controller) {
mCurrentCallState.destroyCalls++;
}
@Override
public void onSaveInstanceState(@NonNull Controller controller, @NonNull Bundle outState) {
mCurrentCallState.saveInstanceStateCalls++;
}
@Override
public void onRestoreInstanceState(@NonNull Controller controller, @NonNull Bundle savedInstanceState) {
mCurrentCallState.restoreInstanceStateCalls++;
}
@Override
public void onSaveViewState(@NonNull Controller controller, @NonNull Bundle outState) {
mCurrentCallState.saveViewStateCalls++;
}
@Override
public void onRestoreViewState(@NonNull Controller controller, @NonNull Bundle savedViewState) {
mCurrentCallState.restoreViewStateCalls++;
}
});
}
interface ChangeHandlerListener {
void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener);
}
public static class ChangeHandler extends ControllerChangeHandler {
private ChangeHandlerListener mListener;
public ChangeHandler() { }
public ChangeHandler(ChangeHandlerListener listener) {
mListener = listener;
}
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
mListener.performChange(container, from, to, isPush, changeListener);
}
}
}
@@ -1,14 +1,13 @@
package com.bluelinelabs.conductor;
import android.app.Activity;
import android.support.annotation.NonNull;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.bluelinelabs.conductor.Controller.LifecycleListener;
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeCompletedListener;
import com.bluelinelabs.conductor.ControllerTransaction.ControllerChangeType;
import com.bluelinelabs.conductor.Controller.RetainViewMode;
import org.junit.Assert;
import org.junit.Before;
@@ -26,240 +25,251 @@ public class ControllerTests {
private ActivityController<TestActivity> mActivityController;
private Router mRouter;
private int mChangeStartCalls;
private int mChangeEndCalls;
private int mBindViewCalls;
private int mAttachCalls;
private int mUnbindViewCalls;
private int mDetachCalls;
private int mDestroyCalls;
public void createActivityController(Bundle savedInstanceState) {
mActivityController = Robolectric.buildActivity(TestActivity.class).create(savedInstanceState).start();
@IdRes int containerId = 4;
FrameLayout routerContainer = new FrameLayout(mActivityController.get());
routerContainer.setId(containerId);
mRouter = Conductor.attachRouter(mActivityController.get(), routerContainer, savedInstanceState);
if (!mRouter.hasRootController()) {
mRouter.setRoot(new TestController());
}
}
@Before
public void setup() {
mActivityController = Robolectric.buildActivity(TestActivity.class).create();
Activity activity = mActivityController.get();
mRouter = Conductor.attachRouter(activity, new FrameLayout(activity), null);
mRouter.setRoot(new TestController());
mChangeStartCalls = 0;
mChangeEndCalls = 0;
mBindViewCalls = 0;
mAttachCalls = 0;
mUnbindViewCalls = 0;
mDestroyCalls = 0;
mDestroyCalls = 0;
createActivityController(null);
}
@Test
public void testNormalLifecycle() {
public void testViewRetention() {
Controller controller = new TestController();
attachLifecycleListener(controller);
assertCalls(0, 0, 0, 0, 0, 0, 0);
mRouter.pushController(RouterTransaction.builder(controller)
.pushChangeHandler(getPushHandler(0, 0, 0, 0, 0, 0, 0))
.popChangeHandler(getPopHandler(1, 1, 1, 1, 0, 0, 0))
.build()
);
// 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());
assertCalls(1, 1, 1, 1, 0, 0, 0);
// 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());
mRouter.popCurrentController();
assertCalls(2, 2, 1, 1, 1, 1, 1);
// Ensure re-setting RELEASE_DETACH releases
controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH);
Assert.assertNull(controller.getView());
}
@Test
public void testLifecycleWithActivityDestroy() {
Controller controller = new TestController();
attachLifecycleListener(controller);
public void testActivityResult() {
TestController controller = new TestController();
CallState expectedCallState = new CallState(true);
assertCalls(0, 0, 0, 0, 0, 0, 0);
mRouter.pushController(RouterTransaction.builder(controller)
.pushChangeHandler(getPushHandler(0, 0, 0, 0, 0, 0, 0))
.build()
);
mRouter.pushController(RouterTransaction.builder(controller).build());
ViewUtils.setAttached(controller.getView(), true);
assertCalls(1, 1, 1, 1, 0, 0, 0);
// Ensure that calling onActivityResult w/o requesting a result doesn't do anything
mRouter.onActivityResult(1, Activity.RESULT_OK, null);
assertCalls(expectedCallState, controller);
mActivityController.pause();
// Ensure starting an activity for result gets us the result back
controller.startActivityForResult(new Intent("action"), 1);
mRouter.onActivityResult(1, Activity.RESULT_OK, null);
expectedCallState.onActivityResultCalls++;
assertCalls(expectedCallState, controller);
assertCalls(1, 1, 1, 1, 0, 0, 0);
mActivityController.stop();
assertCalls(1, 1, 1, 1, 0, 0, 0);
mActivityController.destroy();
assertCalls(1, 1, 1, 1, 0, 0, 1);
// Ensure requesting a result w/o calling startActivityForResult works
controller.registerForActivityResult(2);
mRouter.onActivityResult(2, Activity.RESULT_OK, null);
expectedCallState.onActivityResultCalls++;
assertCalls(expectedCallState, controller);
}
@Test
public void testChildLifecycle() {
Controller parent = new TestController();
mRouter.pushController(RouterTransaction.builder(parent)
.pushChangeHandler(new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
container.addView(to);
ViewUtils.setAttached(to, true);
changeListener.onChangeCompleted();
}
}))
.build());
public void testActivityResultForChild() {
TestController parent = new TestController();
TestController child = new TestController();
Controller child = new TestController();
attachLifecycleListener(child);
mRouter.pushController(RouterTransaction.builder(parent).build());
ViewUtils.setAttached(parent.getView(), true);
parent.addChildController(ChildControllerTransaction.builder(child, TestController.VIEW_ID).build());
ViewUtils.setAttached(child.getView(), true);
assertCalls(0, 0, 0, 0, 0, 0, 0);
CallState childExpectedCallState = new CallState(true);
CallState parentExpectedCallState = new CallState(true);
parent.addChildController(ChildControllerTransaction.builder(child, TestController.VIEW_ID)
.pushChangeHandler(getPushHandler(0, 0, 0, 0, 0, 0, 0))
.popChangeHandler(getPopHandler(1, 1, 1, 1, 0, 0, 0))
.build()
);
// Ensure that calling onActivityResult w/o requesting a result doesn't do anything
mRouter.onActivityResult(1, Activity.RESULT_OK, null);
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
assertCalls(1, 1, 1, 1, 0, 0, 0);
// Ensure starting an activity for result gets us the result back
child.startActivityForResult(new Intent("action"), 1);
mRouter.onActivityResult(1, Activity.RESULT_OK, null);
childExpectedCallState.onActivityResultCalls++;
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
parent.removeChildController(child);
assertCalls(2, 2, 1, 1, 1, 1, 1);
// Ensure requesting a result w/o calling startActivityForResult works
child.registerForActivityResult(2);
mRouter.onActivityResult(2, Activity.RESULT_OK, null);
childExpectedCallState.onActivityResultCalls++;
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
}
@Test
public void testChildLifecycle2() {
Controller parent = new TestController();
mRouter.pushController(RouterTransaction.builder(parent)
.pushChangeHandler(new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
container.addView(to);
ViewUtils.setAttached(to, true);
changeListener.onChangeCompleted();
}
}))
.popChangeHandler(new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
container.removeView(from);
ViewUtils.setAttached(from, false);
changeListener.onChangeCompleted();
}
}))
.build());
public void testPermissionResult() {
final String[] requestedPermissions = new String[] {"test"};
Controller child = new TestController();
attachLifecycleListener(child);
TestController controller = new TestController();
CallState expectedCallState = new CallState(true);
assertCalls(0, 0, 0, 0, 0, 0, 0);
mRouter.pushController(RouterTransaction.builder(controller).build());
ViewUtils.setAttached(controller.getView(), true);
parent.addChildController(ChildControllerTransaction.builder(child, TestController.VIEW_ID)
.pushChangeHandler(getPushHandler(0, 0, 0, 0, 0, 0, 0))
.popChangeHandler(getPopHandler(1, 1, 1, 1, 0, 0, 0))
.build()
);
// Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything
mRouter.onRequestPermissionsResult("anotherId", 1, requestedPermissions, new int[] {1});
assertCalls(expectedCallState, controller);
assertCalls(1, 1, 1, 1, 0, 0, 0);
// Ensure requesting the permission gets us the result back
try {
controller.requestPermissions(requestedPermissions, 1);
} catch (NoSuchMethodError ignored) { }
mRouter.popCurrentController();
ViewUtils.setAttached(child.getView(), false);
assertCalls(1, 1, 1, 1, 1, 1, 1);
mRouter.onRequestPermissionsResult(controller.getInstanceId(), 1, requestedPermissions, new int[] {1});
expectedCallState.onRequestPermissionsResultCalls++;
assertCalls(expectedCallState, controller);
}
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
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
assertCalls(changeStart + 1, changeEnd, bindView + 1, attach, unbindView, detach, destroy);
container.addView(to);
ViewUtils.setAttached(to, true);
assertCalls(changeStart + 1, changeEnd, bindView + 1, attach + 1, unbindView, detach, destroy);
changeListener.onChangeCompleted();
}
});
@Test
public void testPermissionResultForChild() {
final String[] requestedPermissions = new String[] {"test"};
TestController parent = new TestController();
TestController child = new TestController();
mRouter.pushController(RouterTransaction.builder(parent).build());
ViewUtils.setAttached(parent.getView(), true);
parent.addChildController(ChildControllerTransaction.builder(child, TestController.VIEW_ID).build());
ViewUtils.setAttached(child.getView(), true);
CallState childExpectedCallState = new CallState(true);
CallState parentExpectedCallState = new CallState(true);
// Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything
mRouter.onRequestPermissionsResult("anotherId", 1, requestedPermissions, new int[] {1});
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
// Ensure requesting the permission gets us the result back
try {
child.requestPermissions(requestedPermissions, 1);
} catch (NoSuchMethodError ignored) { }
mRouter.onRequestPermissionsResult(child.getInstanceId(), 1, requestedPermissions, new int[] {1});
childExpectedCallState.onRequestPermissionsResultCalls++;
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
}
private ChangeHandler getPopHandler(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
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);
container.removeView(from);
ViewUtils.setAttached(from, false);
assertCalls(changeStart + 1, changeEnd, bindView, attach, unbindView + 1, detach + 1, destroy + 1);
changeListener.onChangeCompleted();
}
});
@Test
public void testOptionsMenu() {
TestController controller = new TestController();
CallState expectedCallState = new CallState(true);
mRouter.pushController(RouterTransaction.builder(controller).build());
ViewUtils.setAttached(controller.getView(), true);
// Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything
mRouter.onCreateOptionsMenu(null, null);
assertCalls(expectedCallState, controller);
// Ensure calling onCreateOptionsMenu with a menu works
controller.setHasOptionsMenu(true);
// Ensure it'll still get called back next time onCreateOptionsMenu is called
mRouter.onCreateOptionsMenu(null, null);
expectedCallState.createOptionsMenuCalls++;
assertCalls(expectedCallState, controller);
// Ensure we stop getting them when we hide it
controller.setOptionsMenuHidden(true);
mRouter.onCreateOptionsMenu(null, null);
assertCalls(expectedCallState, controller);
// Ensure we get the callback them when we un-hide it
controller.setOptionsMenuHidden(false);
mRouter.onCreateOptionsMenu(null, null);
expectedCallState.createOptionsMenuCalls++;
assertCalls(expectedCallState, controller);
// Ensure we don't get the callback when we no longer have a menu
controller.setHasOptionsMenu(false);
mRouter.onCreateOptionsMenu(null, null);
assertCalls(expectedCallState, controller);
}
private void assertCalls(int changeStart, int changeEnd, int bindView, int attach, int unbindView, int detach, int destroy) {
Assert.assertEquals(changeStart, mChangeStartCalls);
Assert.assertEquals(changeEnd, mChangeEndCalls);
Assert.assertEquals(bindView, mBindViewCalls);
Assert.assertEquals(attach, mAttachCalls);
Assert.assertEquals(unbindView, mUnbindViewCalls);
Assert.assertEquals(detach, mDetachCalls);
Assert.assertEquals(destroy, mDestroyCalls);
@Test
public void testOptionsMenuForChild() {
TestController parent = new TestController();
TestController child = new TestController();
mRouter.pushController(RouterTransaction.builder(parent).build());
ViewUtils.setAttached(parent.getView(), true);
parent.addChildController(ChildControllerTransaction.builder(child, TestController.VIEW_ID).build());
ViewUtils.setAttached(child.getView(), true);
CallState childExpectedCallState = new CallState(true);
CallState parentExpectedCallState = new CallState(true);
// Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything
mRouter.onCreateOptionsMenu(null, null);
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
// Ensure calling onCreateOptionsMenu with a menu works
child.setHasOptionsMenu(true);
// Ensure it'll still get called back next time onCreateOptionsMenu is called
mRouter.onCreateOptionsMenu(null, null);
childExpectedCallState.createOptionsMenuCalls++;
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
// Ensure we stop getting them when we hide it
child.setOptionsMenuHidden(true);
mRouter.onCreateOptionsMenu(null, null);
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
// Ensure we get the callback them when we un-hide it
child.setOptionsMenuHidden(false);
mRouter.onCreateOptionsMenu(null, null);
childExpectedCallState.createOptionsMenuCalls++;
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
// Ensure we don't get the callback when we no longer have a menu
child.setHasOptionsMenu(false);
mRouter.onCreateOptionsMenu(null, null);
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
}
private void attachLifecycleListener(Controller controller) {
controller.addLifecycleListener(new LifecycleListener() {
@Override
public void onChangeStart(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
mChangeStartCalls++;
}
@Override
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
mChangeEndCalls++;
}
@Override
public void postBindView(@NonNull Controller controller, @NonNull View view) {
mBindViewCalls++;
}
@Override
public void postAttach(@NonNull Controller controller, @NonNull View view) {
mAttachCalls++;
}
@Override
public void postUnbindView(@NonNull Controller controller) {
mUnbindViewCalls++;
}
@Override
public void postDetach(@NonNull Controller controller, @NonNull View view) {
mDetachCalls++;
}
@Override
public void postDestroy(@NonNull Controller controller) {
mDestroyCalls++;
}
});
private void assertCalls(CallState callState, TestController controller) {
Assert.assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
}
interface ChangeHandlerListener {
void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener);
}
static class ChangeHandler extends ControllerChangeHandler {
private ChangeHandlerListener mListener;
public ChangeHandler() { }
public ChangeHandler(ChangeHandlerListener listener) {
mListener = listener;
}
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
mListener.performChange(container, from, to, isPush, changeListener);
}
}
}
@@ -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.detachAndSaveInstanceState();
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.detachAndSaveInstanceState();
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());
}
}
@@ -3,4 +3,12 @@ package com.bluelinelabs.conductor;
import android.app.Activity;
public class TestActivity extends Activity {
public boolean isChangingConfigurations = false;
@Override
public boolean isChangingConfigurations() {
return isChangingConfigurations;
}
}
@@ -1,24 +1,124 @@
package com.bluelinelabs.conductor;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.bluelinelabs.conductor.ControllerTransaction.ControllerChangeType;
public class TestController extends Controller {
@IdRes public static final int VIEW_ID = 2342;
public TestController() { }
private static final String KEY_CALL_STATE = "TestController.currentCallState";
public CallState currentCallState;
public TestController() {
currentCallState = new CallState();
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
currentCallState.createViewCalls++;
View view = new FrameLayout(inflater.getContext());
view.setId(VIEW_ID);
return view;
}
@Override
protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeStarted(changeHandler, changeType);
currentCallState.changeStartCalls++;
}
@Override
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
currentCallState.changeEndCalls++;
}
@Override
protected void onAttach(@NonNull View view) {
super.onAttach(view);
currentCallState.attachCalls++;
}
@Override
protected void onDetach(@NonNull View view) {
super.onDetach(view);
currentCallState.detachCalls++;
}
@Override
protected void onDestroyView(View view) {
super.onDestroyView(view);
currentCallState.destroyViewCalls++;
}
@Override
protected void onDestroy() {
super.onDestroy();
currentCallState.destroyCalls++;
}
@Override
protected void onSaveViewState(@NonNull View view, @NonNull Bundle outState) {
super.onSaveViewState(view, outState);
currentCallState.saveViewStateCalls++;
}
@Override
protected void onRestoreViewState(@NonNull View view, @NonNull Bundle savedViewState) {
super.onRestoreViewState(view, savedViewState);
currentCallState.restoreViewStateCalls++;
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
currentCallState.saveInstanceStateCalls++;
outState.putParcelable(KEY_CALL_STATE, currentCallState);
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
currentCallState = savedInstanceState.getParcelable(KEY_CALL_STATE);
currentCallState.restoreInstanceStateCalls++;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
currentCallState.onActivityResultCalls++;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
currentCallState.onRequestPermissionsResultCalls++;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
currentCallState.createOptionsMenuCalls++;
}
}
+8 -5
View File
@@ -1,6 +1,6 @@
buildscript {
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
@@ -14,6 +14,10 @@ android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
lintOptions {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
@@ -41,11 +45,10 @@ dependencies {
compile rootProject.ext.butterknife
debugCompile rootProject.ext.leakCanary
releaseCompile rootProject.ext.leakCanaryNoOp
testCompile rootProject.ext.leakCanaryNoOp
compile project(':conductor-support')
compile project(':conductor-rxlifecycle')
debugCompile rootProject.ext.leakCanary
releaseCompile rootProject.ext.leakCanaryNoOp
testCompile rootProject.ext.leakCanaryNoOp
}
+5 -2
View File
@@ -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"
@@ -0,0 +1,7 @@
package com.bluelinelabs.conductor.demo;
import android.support.v7.app.ActionBar;
public interface ActionBarProvider {
ActionBar getSupportActionBar();
}
@@ -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;
@@ -1,7 +1,8 @@
package com.bluelinelabs.conductor.demo;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Conductor;
@@ -11,8 +12,9 @@ import com.bluelinelabs.conductor.demo.controllers.HomeController;
import butterknife.Bind;
import butterknife.ButterKnife;
public class MainActivity extends Activity {
public class MainActivity extends AppCompatActivity implements ActionBarProvider {
@Bind(R.id.toolbar) Toolbar mToolbar;
@Bind(R.id.controller_container) ViewGroup mContainer;
private Router mRouter;
@@ -24,6 +26,8 @@ public class MainActivity extends Activity {
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
setSupportActionBar(mToolbar);
mRouter = Conductor.attachRouter(this, mContainer, savedInstanceState);
if (!mRouter.hasRootController()) {
mRouter.setRoot(new HomeController());
@@ -1,4 +1,4 @@
package com.bluelinelabs.conductor.changehandler;
package com.bluelinelabs.conductor.demo.changehandler;
import android.animation.Animator;
import android.annotation.TargetApi;
@@ -9,6 +9,8 @@ import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.changehandler.AnimatorChangeHandler;
/**
* An {@link AnimatorChangeHandler} that will perform a circular reveal
*/
@@ -0,0 +1,36 @@
package com.bluelinelabs.conductor.demo.changehandler;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.os.Build;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
public class CircularRevealChangeHandlerCompat extends CircularRevealChangeHandler {
public CircularRevealChangeHandlerCompat() { }
public CircularRevealChangeHandlerCompat(@NonNull View fromView, @NonNull View containerView) {
super(fromView, containerView);
}
@Override
protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return super.getAnimator(container, from, to, isPush, toAddedToContainer);
} else {
AnimatorSet animator = new AnimatorSet();
if (to != null && toAddedToContainer) {
animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, 0, 1));
}
if (from != null) {
animator.play(ObjectAnimator.ofFloat(from, View.ALPHA, 0));
}
return animator;
}
}
}
@@ -8,13 +8,13 @@ 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;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.Bind;
public class ChildController extends RefWatchingController {
public class ChildController extends BaseController {
private static final String KEY_TITLE = "ChildController.title";
private static final String KEY_BG_COLOR = "ChildController.bgColor";
@@ -41,8 +41,8 @@ public class ChildController extends RefWatchingController {
}
@Override
protected void onBindView(@NonNull View view) {
super.onBindView(view);
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
mTvTitle.setText(getArgs().getString(KEY_TITLE));
@@ -10,14 +10,14 @@ import android.widget.TextView;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.changehandler.ScaleFadeChangeHandler;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.widget.ElasticDragDismissFrameLayout;
import com.bluelinelabs.conductor.demo.widget.ElasticDragDismissFrameLayout.ElasticDragDismissCallback;
import butterknife.Bind;
@TargetApi(VERSION_CODES.LOLLIPOP)
public class DragDismissController extends RefWatchingController {
public class DragDismissController extends BaseController {
@Bind(R.id.tv_lorem_ipsum) TextView mTvLoremIpsum;
@@ -36,9 +36,7 @@ public class DragDismissController extends RefWatchingController {
}
@Override
protected void onBindView(@NonNull View view) {
super.onBindView(view);
protected void onViewBound(@NonNull View view) {
((ElasticDragDismissFrameLayout)view).addListener(mDragDismissListener);
mTvLoremIpsum.setText("Lorem ipsum dolor sit amet, volutpat lacus egestas integer vitae, tempus potenti posuere dolore, elit cras ut vulputate pede eros. Pharetra curabitur, cum ultrices nisi nulla, non a est diamlorem in pede. Feugiat vivamus id, leo massa, pede ligula libero wisi, posuere nec interdum risus. Mauris eros. Scelerisque etiam dignissim, sem odio magna posuere libero in. Eget non posuere, rutrum nunc ut, ipsum ornare, vestibulum nisl turpis, urna interdum. Arcu mi velit. Sem dolor amet sed hymenaeos tempor. Cras felis.\n" +
@@ -48,9 +46,14 @@ public class DragDismissController extends RefWatchingController {
}
@Override
protected void onUnbindView(View view) {
super.onUnbindView(view);
protected void onDestroyView(View view) {
super.onDestroyView(view);
((ElasticDragDismissFrameLayout)view).removeListener(mDragDismissListener);
}
@Override
protected String getTitle() {
return "Drag to Dismiss";
}
}
@@ -1,28 +1,40 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.content.Intent;
import android.graphics.PorterDuff.Mode;
import android.net.Uri;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.URLSpan;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.bluelinelabs.conductor.ChildControllerTransaction;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerTransaction.ControllerChangeType;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class HomeController extends RefWatchingController {
public class HomeController extends BaseController {
public enum HomeDemoModel {
NAVIGATION("Navigation Demos", R.color.red_300),
@@ -45,6 +57,10 @@ public class HomeController extends RefWatchingController {
@Bind(R.id.recycler_view) RecyclerView mRecyclerView;
public HomeController() {
setHasOptionsMenu(true);
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
@@ -52,14 +68,66 @@ public class HomeController extends RefWatchingController {
}
@Override
protected void onBindView(@NonNull View view) {
super.onBindView(view);
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
mRecyclerView.setAdapter(new HomeAdapter(LayoutInflater.from(view.getContext()), HomeDemoModel.values()));
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.home, menu);
}
@Override
protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
setOptionsMenuHidden(!changeType.isEnter);
if (changeType.isEnter) {
setTitle();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.about) {
SpannableString details = new SpannableString("A small, yet full-featured framework that allows building View-based Android applications");
details.setSpan(new AbsoluteSizeSpan(16, true), 0, details.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
final String url = "https://github.com/bluelinelabs/Conductor";
SpannableString link = new SpannableString(url);
link.setSpan(new URLSpan(url) {
@Override
public void onClick(View widget) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
}, 0, link.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
SpannableStringBuilder content = new SpannableStringBuilder();
content.append("Conductor");
content.append("\n\n");
content.append(details);
content.append("\n\n");
content.append(link);
addChildController(ChildControllerTransaction.builder(new OverlayController(content), R.id.home_root)
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler())
.addToLocalBackstack(true)
.build());
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected String getTitle() {
return "Conductor Demos";
}
void onModelRowClick(HomeDemoModel model) {
switch (model) {
case NAVIGATION:
@@ -91,7 +159,7 @@ public class HomeController extends RefWatchingController {
.build());
break;
case OVERLAY:
addChildController(ChildControllerTransaction.builder(new OverlayController(), R.id.home_root)
addChildController(ChildControllerTransaction.builder(new OverlayController("I'm an Overlay!"), R.id.home_root)
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler())
.addToLocalBackstack(true)
@@ -9,15 +9,15 @@ 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.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import com.bluelinelabs.conductor.demo.util.ColorUtil;
import butterknife.Bind;
import butterknife.OnClick;
public class NavigationDemoController extends RefWatchingController {
public class NavigationDemoController extends BaseController {
public static final String TAG_UP_TRANSACTION = "NavigationDemoController.up";
@@ -45,13 +45,18 @@ public class NavigationDemoController extends RefWatchingController {
}
@Override
protected void onBindView(@NonNull View view) {
super.onBindView(view);
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
view.setBackgroundColor(ColorUtil.getMaterialColor(getResources(), mIndex));
mTvTitle.setText(getResources().getString(R.string.navigation_title, mIndex));
}
@Override
protected String getTitle() {
return "Navigation Demos";
}
@OnClick(R.id.btn_next) void onNextClicked() {
getRouter().pushController(RouterTransaction.builder(new NavigationDemoController(mIndex + 1))
.pushChangeHandler(new HorizontalChangeHandler())
@@ -1,20 +1,35 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import butterknife.Bind;
public class OverlayController extends RefWatchingController {
public class OverlayController extends BaseController {
private static final String KEY_TEXT = "OverlayController.text";
@Bind(R.id.text_view) TextView mTextView;
public OverlayController(CharSequence text) {
this(new BundleBuilder(new Bundle())
.putCharSequence(KEY_TEXT, text)
.build());
}
public OverlayController(Bundle args) {
super(args);
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
@@ -22,9 +37,10 @@ public class OverlayController extends RefWatchingController {
}
@Override
public void onBindView(@NonNull View view) {
super.onBindView(view);
mTextView.setText("I'm an Overlay");
public void onViewBound(@NonNull View view) {
super.onViewBound(view);
mTextView.setText(getArgs().getCharSequence(KEY_TEXT));
mTextView.setMovementMethod(LinkMovementMethod.getInstance());
}
}
@@ -1,6 +1,10 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.support.annotation.NonNull;
import android.support.design.widget.TabLayout;
import android.support.design.widget.TabLayout.OnTabSelectedListener;
import android.support.design.widget.TabLayout.Tab;
import android.support.design.widget.TabLayout.TabLayoutOnPageChangeListener;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
@@ -8,43 +12,64 @@ import android.view.ViewGroup;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.support.ControllerPagerAdapter;
import butterknife.Bind;
public class PagerController extends RefWatchingController {
public class PagerController extends BaseController {
private int[] PAGE_COLORS = new int[]{R.color.green_300, R.color.cyan_300, R.color.deep_purple_300, R.color.lime_300, R.color.red_300};
@Bind(R.id.tab_layout) TabLayout mTabLayout;
@Bind(R.id.view_pager) ViewPager mViewPager;
private ControllerPagerAdapter mPagerAdapter;
private final ControllerPagerAdapter mPagerAdapter;
public PagerController() {
mPagerAdapter = new ControllerPagerAdapter(this) {
@Override
public Controller getItem(int position) {
switch (position) {
case 0:
return new ChildController("Child #1 (Swipe to see more)", R.color.cyan_300, true);
case 1:
return new ChildController("Child #2 (Swipe to see more)", R.color.deep_purple_300, true);
case 2:
return new ChildController("Child #3 (Swipe to see more)", R.color.lime_300, true);
default:
throw new RuntimeException("Invalid item position: " + position);
}
return new ChildController(String.format("Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true);
}
@Override
public int getCount() {
return 3;
return PAGE_COLORS.length;
}
@Override
public CharSequence getPageTitle(int position) {
return "Page " + position;
}
};
}
@Override
protected void onBindView(@NonNull View view) {
super.onBindView(view);
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
mTabLayout.setTabsFromPagerAdapter(mPagerAdapter);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.addOnPageChangeListener(new TabLayoutOnPageChangeListener(mTabLayout));
mTabLayout.setOnTabSelectedListener(new OnTabSelectedListener() {
@Override
public void onTabSelected(Tab tab) {
mViewPager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(Tab tab) { }
@Override
public void onTabReselected(Tab tab) { }
});
}
@Override
protected void onDestroyView(View view) {
mViewPager.setAdapter(null);
super.onDestroyView(view);
}
@NonNull
@@ -53,4 +78,9 @@ public class PagerController extends RefWatchingController {
return inflater.inflate(R.layout.controller_pager, container, false);
}
@Override
protected String getTitle() {
return "ViewPager Demo";
}
}
@@ -11,12 +11,10 @@ import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerTransaction.ControllerChangeType;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.ColorUtil;
import java.util.List;
public class ParentController extends RefWatchingController {
public class ParentController extends BaseController {
private static final int NUMBER_OF_CHILDREN = 5;
private boolean mFinishing;
@@ -37,15 +35,43 @@ 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
protected String getTitle() {
return "Parent/Child Demo";
}
@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 +85,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);
}
}
@@ -10,7 +10,7 @@ import android.widget.TextView;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.rxlifecycle.ControllerEvent;
import java.util.concurrent.TimeUnit;
@@ -23,7 +23,7 @@ import rx.functions.Action1;
// Shamelessly borrowed from the official RxLifecycle demo by Trello and adapted for Conductor Controllers
// instead of Activities or Fragments.
public class RxLifecycleController extends RefWatchingController {
public class RxLifecycleController extends BaseController {
private static final String TAG = "RxLifecycleController";
@@ -48,10 +48,8 @@ public class RxLifecycleController extends RefWatchingController {
}
@Override
public void onBindView(@NonNull View view) {
super.onBindView(view);
Log.i(TAG, "onBindView() called");
public void onViewBound(@NonNull View view) {
Log.i(TAG, "onCreateView() called");
mTvTitle.setText(getResources().getString(R.string.rxlifecycle_title, TAG));
@@ -59,14 +57,14 @@ public class RxLifecycleController extends RefWatchingController {
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
Log.i(TAG, "Unsubscribing from onBindView()");
Log.i(TAG, "Unsubscribing from onCreateView)");
}
})
.compose(this.<Long>bindUntilEvent(ControllerEvent.UNBIND_VIEW))
.compose(this.<Long>bindUntilEvent(ControllerEvent.DESTROY_VIEW))
.subscribe(new Action1<Long>() {
@Override
public void call(Long num) {
Log.i(TAG, "Started in onBindView(), running until onUnbindView(): " + num);
Log.i(TAG, "Started in onCreateView(), running until onDestroyView(): " + num);
}
});
}
@@ -94,10 +92,10 @@ public class RxLifecycleController extends RefWatchingController {
}
@Override
protected void onUnbindView(View view) {
super.onUnbindView(view);
protected void onDestroyView(View view) {
super.onDestroyView(view);
Log.i(TAG, "onUnbindView() called");
Log.i(TAG, "onDestroyView() called");
}
@Override
@@ -114,6 +112,11 @@ public class RxLifecycleController extends RefWatchingController {
Log.i(TAG, "onDestroy() called");
}
@Override
protected String getTitle() {
return "RxLifecycle Demo";
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
@@ -123,7 +126,7 @@ public class RxLifecycleController extends RefWatchingController {
@OnClick(R.id.btn_next_release_view) void onNextWithReleaseClicked() {
setRetainViewMode(RetainViewMode.RELEASE_DETACH);
getRouter().pushController(RouterTransaction.builder(new TextController("Logcat should now report that the observables from onAttach() and onBindView() have been unsubscribed from, while the constructor observable is still running."))
getRouter().pushController(RouterTransaction.builder(new TextController("Logcat should now report that the observables from onAttach() and onViewBound() have been unsubscribed from, while the constructor observable is still running."))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler())
.build()
@@ -133,7 +136,7 @@ public class RxLifecycleController extends RefWatchingController {
@OnClick(R.id.btn_next_retain_view) void onNextWithRetainClicked() {
setRetainViewMode(RetainViewMode.RETAIN_DETACH);
getRouter().pushController(RouterTransaction.builder(new TextController("Logcat should now report that the observables from onAttach() has been unsubscribed from, while the constructor and onBindView() observables are still running."))
getRouter().pushController(RouterTransaction.builder(new TextController("Logcat should now report that the observables from onAttach() has been unsubscribed from, while the constructor and onViewBound() observables are still running."))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler())
.build()
@@ -17,12 +17,12 @@ import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.TargetTitleEntryController.TargetTitleEntryControllerListener;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.Bind;
import butterknife.OnClick;
public class TargetDisplayController extends RefWatchingController implements TargetTitleEntryControllerListener {
public class TargetDisplayController extends BaseController implements TargetTitleEntryControllerListener {
private static final int REQUEST_SELECT_IMAGE = 126;
@@ -70,8 +70,8 @@ public class TargetDisplayController extends RefWatchingController implements Ta
}
@Override
protected void onBindView(@NonNull View view) {
super.onBindView(view);
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
setTextView();
setImageView();
}
@@ -94,6 +94,11 @@ public class TargetDisplayController extends RefWatchingController implements Ta
}
}
@Override
protected String getTitle() {
return "Target Controller Demo";
}
private void setImageView() {
mImageView.setImageURI(mImageUri);
}
@@ -8,12 +8,12 @@ import android.widget.EditText;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.Bind;
import butterknife.OnClick;
public class TargetTitleEntryController extends RefWatchingController {
public class TargetTitleEntryController extends BaseController {
public interface TargetTitleEntryControllerListener {
void onTitlePicked(String option);
@@ -21,12 +21,12 @@ public class TargetTitleEntryController extends RefWatchingController {
@Bind(R.id.edit_text) EditText mEditText;
public TargetTitleEntryController() { }
public TargetTitleEntryController(Controller targetController) {
public <T extends Controller & TargetTitleEntryControllerListener> TargetTitleEntryController(T targetController) {
setTargetController(targetController);
}
public TargetTitleEntryController() { }
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
@@ -34,12 +34,8 @@ public class TargetTitleEntryController extends RefWatchingController {
}
@Override
public void onTargetControllerSet(Controller target) {
super.onTargetControllerSet(target);
if (!(target instanceof TargetTitleEntryControllerListener)) {
throw new RuntimeException(getClass().getSimpleName() + " target Controllers must implement the " + TargetTitleEntryControllerListener.class.getSimpleName() + " interface.");
}
protected String getTitle() {
return "Target Controller Demo";
}
@OnClick(R.id.btn_use_title) void optionPicked() {
@@ -7,13 +7,13 @@ 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;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.Bind;
public class TextController extends RefWatchingController {
public class TextController extends BaseController {
private static final String KEY_TEXT = "TextController.text";
@@ -37,8 +37,8 @@ public class TextController extends RefWatchingController {
}
@Override
public void onBindView(@NonNull View view) {
super.onBindView(view);
public void onViewBound(@NonNull View view) {
super.onViewBound(view);
mTextView.setText(getArgs().getString(KEY_TEXT));
}
@@ -15,27 +15,27 @@ import android.widget.TextView;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.RouterTransaction;
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.R;
import com.bluelinelabs.conductor.demo.changehandler.ArcFadeMoveChangeHandlerCompat;
import com.bluelinelabs.conductor.demo.changehandler.CircularRevealChangeHandlerCompat;
import com.bluelinelabs.conductor.demo.changehandler.FlipChangeHandler;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class TransitionDemoController extends RefWatchingController {
public class TransitionDemoController extends BaseController {
private static final String KEY_INDEX = "TransitionDemoController.index";
public enum TransitionDemo {
VERTICAL("Vertical Slide Animation", R.layout.controller_transition_demo, R.color.blue_grey_300),
CIRCULAR("Circular Reveal Animation", R.layout.controller_transition_demo, R.color.red_300),
CIRCULAR("Circular Reveal Animation (on Lollipop and above, else Fade)", R.layout.controller_transition_demo, R.color.red_300),
FADE("Fade Animation", R.layout.controller_transition_demo, R.color.blue_300),
FLIP("Flip Animation", R.layout.controller_transition_demo, R.color.deep_orange_300),
HORIZONTAL("Horizontal Slide Animation", R.layout.controller_transition_demo, R.color.green_300),
@@ -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,9 +73,15 @@ public class TransitionDemoController extends RefWatchingController {
mTransitionDemo = TransitionDemo.fromIndex(args.getInt(KEY_INDEX));
}
@NonNull
@Override
protected void onBindView(@NonNull View view) {
super.onBindView(view);
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(mTransitionDemo.layoutId, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
View bgView = ButterKnife.findById(view, R.id.bg_view);
if (mTransitionDemo.colorId != 0 && bgView != null) {
@@ -92,14 +98,12 @@ 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);
protected String getTitle() {
return "Transition Demos";
}
@OnClick(R.id.btn_next) void onNextClicked() {
@@ -118,7 +122,7 @@ public class TransitionDemoController extends RefWatchingController {
return new VerticalChangeHandler();
case CIRCULAR:
TransitionDemoController demoController = (TransitionDemoController)from;
return new CircularRevealChangeHandler(demoController.mBtnNext, demoController.mContainerView);
return new CircularRevealChangeHandlerCompat(demoController.mBtnNext, demoController.mContainerView);
case FADE:
return new FadeChangeHandler();
case FLIP:
@@ -143,4 +147,5 @@ public class TransitionDemoController extends RefWatchingController {
.popChangeHandler(changeHandler)
.build();
}
}
@@ -0,0 +1,42 @@
package com.bluelinelabs.conductor.demo.controllers.base;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
import android.view.View;
import com.bluelinelabs.conductor.demo.ActionBarProvider;
public abstract class BaseController extends RefWatchingController {
protected BaseController() { }
protected BaseController(Bundle args) {
super(args);
}
// Note: This is just a quick demo of how an ActionBar *can* be accessed, not necessarily how it *should*
// be accessed. In a production app, this would use Dagger instead.
protected ActionBar getActionBar() {
ActionBarProvider actionBarProvider = ((ActionBarProvider)getActivity());
return actionBarProvider != null ? actionBarProvider.getSupportActionBar() : null;
}
@Override
protected void onAttach(@NonNull View view) {
setTitle();
super.onAttach(view);
}
protected void setTitle() {
String title = getTitle();
ActionBar actionBar = getActionBar();
if (title != null && actionBar != null) {
actionBar.setTitle(title);
}
}
protected String getTitle() {
return null;
}
}
@@ -2,7 +2,9 @@ package com.bluelinelabs.conductor.demo.controllers.base;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.rxlifecycle.RxController;
@@ -15,16 +17,23 @@ public abstract class ButterKnifeController extends RxController {
super(args);
}
protected abstract View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container);
@NonNull
@Override
protected void onBindView(@NonNull View view) {
super.onBindView(view);
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
View view = inflateView(inflater, container);
ButterKnife.bind(this, view);
onViewBound(view);
return view;
}
protected void onViewBound(@NonNull View view) { }
@Override
protected void onUnbindView(View view) {
super.onUnbindView(view);
protected void onDestroyView(View view) {
super.onDestroyView(view);
ButterKnife.unbind(this);
}
}
}
@@ -1,10 +1,8 @@
package com.bluelinelabs.conductor.demo.controllers.base;
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 {
@@ -13,21 +11,10 @@ public abstract class RefWatchingController extends ButterKnifeController {
super(args);
}
@Override
protected void onDetach(@NonNull View view) {
super.onDetach(view);
if (isDestroyed()) {
App.refWatcher.watch(view);
}
}
@Override
public void onDestroy() {
if (getView() != null) {
App.refWatcher.watch(getView());
}
App.refWatcher.watch(this);
super.onDestroy();
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;
@@ -16,13 +16,12 @@
package com.bluelinelabs.conductor.demo.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION_CODES;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import java.util.ArrayList;
@@ -33,8 +32,30 @@ import java.util.List;
* Applies an elasticity factor to reduce movement as you approach the given dismiss distance.
* Optionally also scales down content during drag.
*/
@TargetApi(VERSION_CODES.LOLLIPOP)
public class ElasticDragDismissFrameLayout extends FrameLayout {
public class ElasticDragDismissFrameLayout extends FrameLayout implements NestedScrollingParent {
public static abstract class ElasticDragDismissCallback {
/**
* Called for each drag event.
*
* @param elasticOffset Indicating the drag offset with elasticity applied i.e. may
* exceed 1.
* @param elasticOffsetPixels The elastically scaled drag distance in pixels.
* @param rawOffset Value from [0, 1] indicating the raw drag offset i.e.
* without elasticity applied. A value of 1 indicates that the
* dismiss distance has been reached.
* @param rawOffsetPixels The raw distance the user has dragged
*/
public void onDrag(float elasticOffset, float elasticOffsetPixels,
float rawOffset, float rawOffsetPixels) { }
/**
* Called when dragging is released and has exceeded the threshold dismiss distance.
*/
public void onDragDismissed() { }
}
// configurable attribs
private float dragDismissDistance = Float.MAX_VALUE;
@@ -51,21 +72,16 @@ public class ElasticDragDismissFrameLayout extends FrameLayout {
private List<ElasticDragDismissCallback> callbacks;
public ElasticDragDismissFrameLayout(Context context) {
this(context, null, 0, 0);
this(context, null, 0);
}
public ElasticDragDismissFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0, 0);
this(context, attrs, 0);
}
public ElasticDragDismissFrameLayout(Context context, AttributeSet attrs,
int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public ElasticDragDismissFrameLayout(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
super(context, attrs, defStyleAttr);
dragDismissDistance = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80, getResources().getDisplayMetrics());
dragDismissFraction = 0.7f;
@@ -73,29 +89,6 @@ public class ElasticDragDismissFrameLayout extends FrameLayout {
shouldScale = true;
}
public static abstract class ElasticDragDismissCallback {
/**
* Called for each drag event.
*
* @param elasticOffset Indicating the drag offset with elasticity applied i.e. may
* exceed 1.
* @param elasticOffsetPixels The elastically scaled drag distance in pixels.
* @param rawOffset Value from [0, 1] indicating the raw drag offset i.e.
* without elasticity applied. A value of 1 indicates that the
* dismiss distance has been reached.
* @param rawOffsetPixels The raw distance the user has dragged
*/
public void onDrag(float elasticOffset, float elasticOffsetPixels,
float rawOffset, float rawOffsetPixels) { }
/**
* Called when dragging is released and has exceeded the threshold dismiss distance.
*/
public void onDragDismissed() { }
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return (nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0;
@@ -126,7 +119,7 @@ public class ElasticDragDismissFrameLayout extends FrameLayout {
.scaleX(1f)
.scaleY(1f)
.setDuration(200L)
.setInterpolator(AnimationUtils.loadInterpolator(getContext(), android.R.interpolator.fast_out_slow_in))
.setInterpolator(new FastOutSlowInInterpolator())
.setListener(null)
.start();
totalDrag = 0;
@@ -135,6 +128,24 @@ public class ElasticDragDismissFrameLayout extends FrameLayout {
}
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return false;
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return false;
}
@Override
public int getNestedScrollAxes() {
return 0;
}
@Override
public void onNestedScrollAccepted(View child, View target, int axes) { }
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
@@ -171,6 +182,9 @@ public class ElasticDragDismissFrameLayout extends FrameLayout {
draggingUp = true;
if (shouldScale) setPivotY(0f);
}
setPivotX(getWidth() / 2);
// how far have we dragged relative to the distance to perform a dismiss
// (01 where 1 = dismiss distance). Decreasing logarithmically as we approach the limit
float dragFraction = (float) Math.log10(1 + (Math.abs(totalDrag) / dragDismissDistance));
+26 -4
View File
@@ -1,9 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<com.bluelinelabs.conductor.ChangeHandlerFrameLayout
android:id="@+id/controller_container"
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.bluelinelabs.conductor.demo.MainActivity"
/>
android:fitsSystemWindows="true"
tools:context="com.bluelinelabs.conductor.demo.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
/>
</android.support.design.widget.AppBarLayout>
<com.bluelinelabs.conductor.ChangeHandlerFrameLayout
android:id="@+id/controller_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>
</android.support.design.widget.CoordinatorLayout>
@@ -14,17 +14,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="28sp"
android:textSize="22sp"
android:padding="16dp"
android:text="@string/drag_to_dismiss"
/>
<ScrollView
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:fillViewport="true"
android:nestedScrollingEnabled="true"
android:overScrollFooter="@android:color/transparent"
android:overScrollMode="never" >
@@ -36,7 +35,7 @@
android:padding="16dp"
/>
</ScrollView>
</android.support.v4.widget.NestedScrollView>
</LinearLayout>
</com.bluelinelabs.conductor.demo.widget.ElasticDragDismissFrameLayout>
@@ -4,20 +4,21 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#88000000"
android:clickable="true" >
android:clickable="true">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="32dp"
android:background="@android:color/white" >
android:background="@android:color/white">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="16dp"
/>
</FrameLayout>
@@ -3,16 +3,16 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/green_300"
android:orientation="vertical" >
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="28sp"
android:padding="16dp"
android:text="@string/view_pager"
android:background="?attr/colorPrimary"
android:elevation="6dp"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
/>
<android.support.v4.view.ViewPager
@@ -16,10 +16,10 @@
<ImageView
android:id="@+id/image_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_width="@dimen/display_target_image_size"
android:layout_height="@dimen/display_target_image_size"
android:layout_gravity="center_horizontal"
android:layout_margin="24dp"
android:layout_margin="@dimen/display_target_image_margin"
/>
<LinearLayout
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AlwaysShowAction">
<item
android:id="@+id/about"
android:title="@string/about"
app:showAsAction="always"
/>
</menu>
+8
View File
@@ -0,0 +1,8 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">24dp</dimen>
<dimen name="activity_vertical_margin">24dp</dimen>
<dimen name="display_target_image_size">120dp</dimen>
<dimen name="display_target_image_margin">16dp</dimen>
</resources>
+2 -2
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#78909c</color>
<color name="colorPrimaryDark">#37474f</color>
<color name="colorPrimary">#263239</color>
<color name="colorPrimaryDark">#21272c</color>
<color name="colorAccent">#1976d2</color>
<color name="white">@android:color/white</color>
+3
View File
@@ -2,4 +2,7 @@
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="display_target_image_size">200dp</dimen>
<dimen name="display_target_image_margin">24dp</dimen>
</resources>
+4 -4
View File
@@ -2,6 +2,9 @@
<resources>
<string name="app_name">Conductor Demo</string>
<!-- Home Controller -->
<string name="about">About</string>
<!-- Navigation Demo -->
<string name="pop_to_root">Pop to Root</string>
<string name="go_up">Go Up</string>
@@ -13,9 +16,6 @@
<string name="transition_tag_dot">transition.dot</string>
<string name="transition_tag_title">transition.title</string>
<!-- View Pager Demo -->
<string name="view_pager">View Pager</string>
<!-- Parent Controller Demo -->
<string name="parent_controller">Parent Controller</string>
<string name="go_to_next_parent">Go to next parent</string>
@@ -27,7 +27,7 @@
<string name="use_title">Use Title</string>
<!-- Drag Dismiss Demo -->
<string name="drag_to_dismiss">Drag to Dismiss</string>
<string name="drag_to_dismiss">Elastic scrolling view - scroll past bounds to dismiss.</string>
<!-- RxLifecycle Demo -->
<string name="rxlifecycle_title">Watch tag %1$s on logcat for a demo.</string>
+2 -2
View File
@@ -5,10 +5,10 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowBackground">@color/white</item>
<item name="android:textViewStyle">@style/TextView</item>
<item name="android:textViewStyle">@style/AppTheme.TextView</item>
</style>
<style name="TextView" parent="@android:style/Widget.TextView">
<style name="AppTheme.TextView" parent="@android:style/Widget.TextView">
<item name="android:textSize">20sp</item>
</style>
+7 -7
View File
@@ -4,9 +4,6 @@ ext {
targetSdkVersion = 23
buildToolsVersion = '23.0.2'
versionCode = 1
versionName = '1.0.1'
supportV4 = 'com.android.support:support-v4:23.1.1'
supportDesign = 'com.android.support:design:23.1.1'
supportAnnotations = 'com.android.support:support-annotations:23.1.1'
@@ -14,13 +11,16 @@ ext {
butterknife = 'com.jakewharton:butterknife:7.0.1'
leakCanary = 'com.squareup.leakcanary:leakcanary-android:1.4-beta1'
leakCanaryNoOp = 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
leakCanary = 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
leakCanaryNoOp = 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
rxJava = 'io.reactivex:rxjava:1.1.0'
rxAndroid = 'io.reactivex:rxandroid:1.1.0'
rxLifecycle = 'com.trello:rxlifecycle:0.5.0'
rxLifecycle = 'com.trello:rxlifecycle:0.6.0'
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: 103 KiB

+124
View File
@@ -0,0 +1,124 @@
/*
* Copyright 2013 Chris Banes
*
* 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.
*/
apply plugin: 'maven'
apply plugin: 'signing'
def isReleaseBuild() {
return VERSION_NAME.contains("SNAPSHOT") == false
}
def getReleaseRepositoryUrl() {
return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL :
"https://oss.sonatype.org/service/local/staging/deploy/maven2/"
}
def getSnapshotRepositoryUrl() {
return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL :
"https://oss.sonatype.org/content/repositories/snapshots/"
}
def getRepositoryUsername() {
return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
}
def getRepositoryPassword() {
return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
}
afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
pom.groupId = GROUP
pom.artifactId = POM_ARTIFACT_ID
pom.version = VERSION_NAME
repository(url: getReleaseRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
snapshotRepository(url: getSnapshotRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
pom.project {
name POM_NAME
packaging POM_PACKAGING
description POM_DESCRIPTION
url POM_URL
scm {
url POM_SCM_URL
connection POM_SCM_CONNECTION
developerConnection POM_SCM_DEV_CONNECTION
}
licenses {
license {
name POM_LICENCE_NAME
url POM_LICENCE_URL
distribution POM_LICENCE_DIST
}
}
developers {
developer {
id POM_DEVELOPER_ID
name POM_DEVELOPER_NAME
}
}
}
// Resolve dependencies to other modules
pom.whenConfigured { pom ->
pom.dependencies.findAll { dep -> dep.groupId == rootProject.name }.collect { dep ->
dep.groupId = pom.groupId = project.GROUP
dep.version = pom.version = project.VERSION_NAME
}
}
}
}
}
signing {
required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
task androidJavadocs(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
failOnError = false
}
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}
artifacts {
archives androidSourcesJar
archives androidJavadocsJar
}
}
+14
View File
@@ -0,0 +1,14 @@
VERSION_NAME=1.1.7-SNAPSHOT
VERSION_CODE=2
GROUP=com.bluelinelabs
POM_DESCRIPTION=A small, yet full-featured framework that allows building View-based Android applications
POM_URL=https://github.com/bluelinelabs/Conductor
POM_SCM_URL=https://github.com/bluelinelabs/Conductor
POM_SCM_CONNECTION=scm:git:https://github.com/bluelinelabs/Conductor
POM_SCM_DEV_CONNECTION=scm:git:https://github.com/bluelinelabs/Conductor
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=erickuck
POM_DEVELOPER_NAME=Eric Kuck
+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'