├── featured ├── proguard-rules.txt ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── de │ │ └── halfbit │ │ └── featured │ │ ├── FeatureHost.java │ │ └── Feature.java ├── gradle.properties └── build.gradle ├── featured-sample-library ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── de │ │ └── halfbit │ │ └── featured │ │ └── sample │ │ └── library │ │ └── LifecycleFeature.java ├── proguard-rules.pro └── build.gradle ├── docs ├── images │ └── diagram.png ├── migrate-to-0.1.0.md ├── featured-design.md └── quick-start.md ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── gradle-mvn-push.gradle ├── featured-compiler ├── gradle.properties ├── src │ ├── test │ │ └── java │ │ │ ├── android │ │ │ ├── app │ │ │ │ └── Application.java │ │ │ └── content │ │ │ │ └── Context.java │ │ │ └── de │ │ │ └── halfbit │ │ │ └── featured │ │ │ ├── Feature.java │ │ │ ├── FeatureHost.java │ │ │ └── compiler │ │ │ └── FeatureProcessorTests.java │ └── main │ │ └── java │ │ └── de │ │ └── halfbit │ │ └── featured │ │ └── compiler │ │ ├── Assertions.java │ │ ├── model │ │ ├── ModelNodeVisitor.java │ │ ├── ParameterNode.java │ │ ├── MethodNode.java │ │ ├── FeatureNode.java │ │ └── ModelNode.java │ │ ├── FeatureModelValidator.java │ │ ├── FeatureProcessor.java │ │ ├── FeatureCodeBrewer.java │ │ └── Names.java └── build.gradle ├── featured-annotations ├── gradle.properties ├── build.gradle └── src │ └── main │ └── java │ └── de │ └── halfbit │ └── featured │ └── FeatureEvent.java ├── featured-sample ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ └── layout │ │ │ │ └── activity_sample.xml │ │ ├── java │ │ │ └── de │ │ │ │ └── halfbit │ │ │ │ └── featured │ │ │ │ └── sample │ │ │ │ ├── library │ │ │ │ ├── SampleLibraryFeature.java │ │ │ │ └── SampleLibraryActivity.java │ │ │ │ ├── features │ │ │ │ ├── ToolbarFeature.java │ │ │ │ ├── FabFeature.java │ │ │ │ ├── ToastFeature.java │ │ │ │ ├── SnackbarFeature.java │ │ │ │ └── LoggerFeature.java │ │ │ │ ├── util │ │ │ │ └── Utils.java │ │ │ │ ├── SampleFeature.java │ │ │ │ └── SampleActivity.java │ │ └── AndroidManifest.xml │ └── test │ │ └── java │ │ └── de │ │ └── halfbit │ │ └── featured │ │ └── sample │ │ └── library │ │ ├── EventCollector.java │ │ ├── TestLifecycleFeature.java │ │ ├── TestSampleLibraryFeature.java │ │ └── FeatureImplementationTest.java └── build.gradle ├── settings.gradle ├── .travis.yml ├── .gitignore ├── gradle.properties ├── versions.gradle ├── README.md ├── gradlew.bat ├── checkstyle.xml ├── gradlew └── LICENSE /featured/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # nop -------------------------------------------------------------------------------- /featured-sample-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /featured/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/images/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergejsha/featured/HEAD/docs/images/diagram.png -------------------------------------------------------------------------------- /featured/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=featured 2 | POM_NAME=Featured 3 | POM_PACKAGING=aar 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergejsha/featured/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /featured-compiler/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Featured Compiler 2 | POM_ARTIFACT_ID=featured-compiler 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /featured-annotations/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Featured Annotations 2 | POM_ARTIFACT_ID=featured-annotations 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /featured-sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Featured Sample 3 | 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':featured', ':featured-annotations', ':featured-compiler', 2 | ':featured-sample', ':featured-sample-library' -------------------------------------------------------------------------------- /featured-sample-library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | featured-sample-library 3 | 4 | -------------------------------------------------------------------------------- /featured-compiler/src/test/java/android/app/Application.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | // Dummy class for tests to compile 4 | public class Application { 5 | } 6 | -------------------------------------------------------------------------------- /featured-compiler/src/test/java/android/content/Context.java: -------------------------------------------------------------------------------- 1 | package android.content; 2 | 3 | // Dummy class for tests to compile 4 | public class Context { 5 | } 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Aug 16 16:17:23 CEST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /featured-sample-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /featured-compiler/src/main/java/de/halfbit/featured/compiler/Assertions.java: -------------------------------------------------------------------------------- 1 | package de.halfbit.featured.compiler; 2 | 3 | import de.halfbit.featured.compiler.model.FeatureNode; 4 | 5 | public class Assertions { 6 | 7 | private Assertions() { 8 | } 9 | 10 | public static S assertNotNull(S subj, FeatureNode featureNode) { 11 | if (subj == null) { 12 | throw new IllegalArgumentException("Subject is null in " + featureNode); 13 | } 14 | return subj; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | install: true 3 | 4 | jdk: 5 | - oraclejdk8 6 | 7 | android: 8 | components: 9 | - tools 10 | - platform-tools 11 | - build-tools-28.0.3 12 | - android-28 13 | - extra-android-m2repository 14 | 15 | before_install: 16 | - yes | sdkmanager "platforms;android-28" 17 | 18 | before_cache: 19 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 20 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 21 | 22 | cache: 23 | directories: 24 | - $HOME/.gradle/caches/ 25 | - $HOME/.gradle/wrapper/ 26 | 27 | script: 28 | - ./gradlew clean build --stacktrace 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/ 38 | 39 | # Keystore files 40 | *.jks 41 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=de.halfbit 2 | VERSION_NAME=0.3.0 3 | VERSION_CODE=6 4 | 5 | POM_DESCRIPTION=Simple yet powerful composition library for Android. 6 | 7 | POM_URL=https://github.com/halfbit/featured/ 8 | POM_SCM_URL=https://github.com/halfbit/featured/ 9 | POM_SCM_CONNECTION=scm:git:git://github.com/halfbit/featured.git 10 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/halfbit/featured.git 11 | 12 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 13 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 14 | POM_LICENCE_DIST=repo 15 | 16 | POM_DEVELOPER_ID=beworker 17 | POM_DEVELOPER_NAME=Sergej Shafarenka -------------------------------------------------------------------------------- /featured-sample/src/test/java/de/halfbit/featured/sample/library/EventCollector.java: -------------------------------------------------------------------------------- 1 | package de.halfbit.featured.sample.library; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import static com.google.common.truth.Truth.assertThat; 7 | 8 | public class EventCollector { 9 | 10 | private List mEvents = new ArrayList<>(10); 11 | 12 | public void onEvent(Object event) { 13 | mEvents.add(event); 14 | } 15 | 16 | public void assertEvents(String... events) { 17 | assertThat(mEvents) 18 | .containsExactly((Object[]) events) 19 | .inOrder(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /featured-annotations/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'checkstyle' 3 | 4 | def logger = new com.android.build.gradle.internal.LoggerWrapper(project.logger) 5 | def sdkHandler = new com.android.build.gradle.internal.SdkHandler(project, logger) 6 | for (File file : sdkHandler.sdkLoader.repositories) { 7 | repositories.maven { 8 | url = file.toURI() 9 | } 10 | } 11 | 12 | sourceCompatibility = rootProject.ext.sourceCompatibilityVersion 13 | targetCompatibility = rootProject.ext.targetCompatibilityVersion 14 | 15 | checkstyle { 16 | configFile rootProject.file('checkstyle.xml') 17 | showViolations true 18 | } 19 | 20 | dependencies { 21 | } 22 | 23 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 24 | -------------------------------------------------------------------------------- /featured/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | 6 | defaultConfig { 7 | minSdkVersion rootProject.ext.minSdkVersion 8 | 9 | consumerProguardFiles 'proguard-rules.txt' 10 | } 11 | 12 | lintOptions { 13 | textReport true 14 | textOutput 'stdout' 15 | } 16 | } 17 | 18 | dependencies { 19 | compileOnly deps.android 20 | 21 | api project(':featured-annotations') 22 | api deps.jetbrainsAnnotations 23 | 24 | testImplementation deps.junit 25 | testImplementation deps.truth 26 | testImplementation deps.robolectric 27 | } 28 | 29 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 30 | -------------------------------------------------------------------------------- /featured-sample-library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/sergej/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /featured-sample/src/main/java/de/halfbit/featured/sample/library/SampleLibraryFeature.java: -------------------------------------------------------------------------------- 1 | package de.halfbit.featured.sample.library; 2 | 3 | import android.app.Activity; 4 | 5 | import de.halfbit.featured.FeatureEvent; 6 | 7 | /** 8 | * This application feature extends a library feature and inherits its whole lifecycle. 9 | * Generated feature host will include dispatch methods for {@code SampleLibraryFeature} 10 | * as well as {@code LifecycleFeature} events. 11 | */ 12 | public class SampleLibraryFeature extends LifecycleFeature { 13 | 14 | @FeatureEvent 15 | public void onDataLoaded() { 16 | // nop 17 | } 18 | 19 | @FeatureEvent 20 | public void onUiShown() { 21 | // nop 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /featured-sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #3F51B5 5 | #303F9F 6 | #C5CAE9 7 | #FF5722 8 | #212121 9 | #727272 10 | #FFFFFF 11 | #B6B6B6 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /featured-sample-library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | 6 | compileOptions { 7 | sourceCompatibility = rootProject.ext.sourceCompatibilityVersion 8 | targetCompatibility = rootProject.ext.targetCompatibilityVersion 9 | } 10 | 11 | defaultConfig { 12 | minSdkVersion rootProject.ext.minSdkVersion 13 | targetSdkVersion rootProject.ext.targetSdkVersion 14 | versionCode 5 15 | versionName "0.2.0" 16 | } 17 | 18 | buildTypes { 19 | debug { 20 | minifyEnabled true 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | annotationProcessor project(':featured-compiler') 27 | implementation project(':featured') 28 | } 29 | -------------------------------------------------------------------------------- /featured-sample/src/test/java/de/halfbit/featured/sample/library/TestLifecycleFeature.java: -------------------------------------------------------------------------------- 1 | package de.halfbit.featured.sample.library; 2 | 3 | public class TestLifecycleFeature extends LifecycleFeature { 4 | 5 | private EventCollector mEventCollector = new EventCollector(); 6 | 7 | @Override protected void onCreate() { 8 | mEventCollector.onEvent("onCreate"); 9 | } 10 | 11 | @Override protected void onStart() { 12 | mEventCollector.onEvent("onStart"); 13 | } 14 | 15 | @Override protected void onStop() { 16 | mEventCollector.onEvent("onStop"); 17 | } 18 | 19 | @Override protected void onDestroy() { 20 | mEventCollector.onEvent("onDestroy"); 21 | } 22 | 23 | public void assertEvents(String... events) { 24 | mEventCollector.assertEvents(events); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /featured-sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /featured-compiler/src/test/java/de/halfbit/featured/Feature.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package de.halfbit.featured; 17 | 18 | // Dummy class for tests to compile 19 | public abstract class Feature { 20 | 21 | void attachFeatureHost(FH featureHost) { 22 | throw new RuntimeException("Stub!"); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /featured-sample/src/main/java/de/halfbit/featured/sample/library/SampleLibraryActivity.java: -------------------------------------------------------------------------------- 1 | package de.halfbit.featured.sample.library; 2 | 3 | import android.os.Bundle; 4 | import android.os.PersistableBundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | /** 8 | * This is just a demo activity showing how you can combine features inheriting same 9 | * lifecycle in a single feature host. Feature host is capable to dispatch all events. 10 | * Each registered feature will only receive events belonging to its lifecycle. 11 | */ 12 | public class SampleLibraryActivity extends AppCompatActivity { 13 | 14 | @Override public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) { 15 | SampleLibraryFeatureHost host = new SampleLibraryFeatureHost(this) 16 | .with(new LifecycleFeature()) 17 | .with(new SampleLibraryFeature()); 18 | 19 | host.dispatchOnCreate(); 20 | host.dispatchOnDataLoaded(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /featured-sample/src/main/java/de/halfbit/featured/sample/features/ToolbarFeature.java: -------------------------------------------------------------------------------- 1 | package de.halfbit.featured.sample.features; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.support.design.widget.CoordinatorLayout; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.Toolbar; 9 | 10 | import de.halfbit.featured.sample.R; 11 | import de.halfbit.featured.sample.SampleFeature; 12 | import de.halfbit.featured.sample.util.Utils; 13 | 14 | public class ToolbarFeature extends SampleFeature { 15 | 16 | private Toolbar mToolbar; 17 | 18 | @Override 19 | protected void onCreate(@NonNull CoordinatorLayout parent, 20 | @Nullable Bundle savedInstanceState) { 21 | mToolbar = Utils.findAndShowView(parent, R.id.toolbar); 22 | 23 | AppCompatActivity activity = Utils.getActivity(parent.getContext()); 24 | activity.setSupportActionBar(mToolbar); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /featured-sample/src/main/java/de/halfbit/featured/sample/features/FabFeature.java: -------------------------------------------------------------------------------- 1 | package de.halfbit.featured.sample.features; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.support.design.widget.CoordinatorLayout; 7 | import android.support.design.widget.FloatingActionButton; 8 | import android.view.View; 9 | 10 | import de.halfbit.featured.sample.R; 11 | import de.halfbit.featured.sample.SampleFeature; 12 | import de.halfbit.featured.sample.util.Utils; 13 | 14 | public class FabFeature extends SampleFeature implements View.OnClickListener { 15 | 16 | private FloatingActionButton mButton; 17 | 18 | @Override 19 | protected void onCreate(@NonNull CoordinatorLayout parent, 20 | @Nullable Bundle savedInstanceState) { 21 | mButton = Utils.findAndShowView(parent, R.id.fab, this); 22 | } 23 | 24 | @Override public void onClick(View view) { 25 | getFeatureHost().dispatchOnFabClicked(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /featured-sample-library/src/main/java/de/halfbit/featured/sample/library/LifecycleFeature.java: -------------------------------------------------------------------------------- 1 | package de.halfbit.featured.sample.library; 2 | 3 | import android.app.Activity; 4 | 5 | import de.halfbit.featured.Feature; 6 | import de.halfbit.featured.FeatureEvent; 7 | 8 | /** 9 | * This is a base lifecycle feature defined in a library. It can be used 10 | * in other project. Just import the library and extend this feature 11 | * to get access to its callbacks. See how it is done in {@code SampleLibraryFeature} 12 | * in featured-sample project. 13 | */ 14 | public class LifecycleFeature 15 | extends Feature { 16 | 17 | @FeatureEvent 18 | protected void onCreate() { 19 | // nop 20 | } 21 | 22 | @FeatureEvent 23 | protected void onStart() { 24 | // nop 25 | } 26 | 27 | @FeatureEvent 28 | protected void onStop() { 29 | // nop 30 | } 31 | 32 | @FeatureEvent 33 | protected void onDestroy() { 34 | // nop 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /featured-sample/src/test/java/de/halfbit/featured/sample/library/TestSampleLibraryFeature.java: -------------------------------------------------------------------------------- 1 | package de.halfbit.featured.sample.library; 2 | 3 | public class TestSampleLibraryFeature extends SampleLibraryFeature { 4 | 5 | private EventCollector mEventCollector = new EventCollector(); 6 | 7 | @Override protected void onCreate() { 8 | mEventCollector.onEvent("onCreate"); 9 | } 10 | 11 | @Override protected void onStart() { 12 | mEventCollector.onEvent("onStart"); 13 | } 14 | 15 | @Override protected void onStop() { 16 | mEventCollector.onEvent("onStop"); 17 | } 18 | 19 | @Override protected void onDestroy() { 20 | mEventCollector.onEvent("onDestroy"); 21 | } 22 | 23 | @Override public void onDataLoaded() { 24 | mEventCollector.onEvent("onDataLoaded"); 25 | } 26 | 27 | @Override public void onUiShown() { 28 | mEventCollector.onEvent("onUiShown"); 29 | } 30 | 31 | public void assertEvents(String... events) { 32 | mEventCollector.assertEvents(events); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /featured-compiler/src/main/java/de/halfbit/featured/compiler/model/ModelNodeVisitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package de.halfbit.featured.compiler.model; 17 | 18 | public interface ModelNodeVisitor { 19 | boolean onFeatureEnter(FeatureNode feature); 20 | void onMethodEnter(MethodNode method); 21 | void onParameter(ParameterNode param); 22 | void onMethodExit(MethodNode method); 23 | void onFeatureExit(FeatureNode feature); 24 | } 25 | -------------------------------------------------------------------------------- /docs/migrate-to-0.1.0.md: -------------------------------------------------------------------------------- 1 | # Migrate to version 0.1.0 2 | 3 | Main API change in this release is the additional parameter required by `Feature` class. This is a type of context your feature expects. Before the version 0.1.0 you could access context inside a feature through the feature host by calling `getFeatureHost().getContext()` methods. Now the context type belongs to feature contract and, as a result, shall be parametrized and accessed through feature class. 4 | 5 | # Code changes 6 | 7 | 1. Add context type parameter to feature class. 8 | 9 | For example you had a feature like this. 10 | ``` 11 | public class MyFeature extends Feature { 12 | } 13 | ``` 14 | 15 | Just add a context type parameter as following. 16 | ``` 17 | public class MyFeature extends Feature { 18 | } 19 | ``` 20 | 21 | You can use any class as a context. Typical types of choice are `Context` or `Activity`, but you can use any type you want. 22 | 23 | 2. Replace `getFeatureHost().getContext()` with `getContext()` 24 | 25 | 3. Build -> Clean Project 26 | 27 | 4. Build -> Rebuild Project 28 | 29 | Your code must compile and run again. 30 | -------------------------------------------------------------------------------- /versions.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | minSdkVersion = 14 3 | targetSdkVersion = 28 4 | compileSdkVersion = 28 5 | sourceCompatibilityVersion = JavaVersion.VERSION_1_7 6 | targetCompatibilityVersion = JavaVersion.VERSION_1_7 7 | } 8 | 9 | ext.deps = [:] 10 | 11 | // android 12 | ext.deps.android = 'com.google.android:android:4.1.1.4' 13 | ext.deps.supportv4 = 'com.android.support:support-v4:28.0.0' 14 | ext.deps.supportAnnotations = 'com.android.support:support-annotations:28.0.0' 15 | ext.deps.supportAppCompat = 'com.android.support:appcompat-v7:28.0.0' 16 | ext.deps.supportDesign = 'com.android.support:design:28.0.0' 17 | 18 | // open source 19 | ext.deps.javapoet = 'com.squareup:javapoet:1.7.0' 20 | ext.deps.jetbrainsAnnotations = 'org.jetbrains:annotations:15.0' 21 | 22 | // testing 23 | ext.deps.junit = 'junit:junit:4.12' 24 | ext.deps.truth = 'com.google.truth:truth:0.28' 25 | ext.deps.robolectric = 'org.robolectric:robolectric:3.1' 26 | ext.deps.compiletesting = 'com.google.testing.compile:compile-testing:0.9' 27 | ext.deps.autoservice = 'com.google.auto.service:auto-service:1.0-rc4' 28 | ext.deps.autocommon = 'com.google.auto:auto-common:0.10' 29 | -------------------------------------------------------------------------------- /featured-sample/src/main/java/de/halfbit/featured/sample/features/ToastFeature.java: -------------------------------------------------------------------------------- 1 | package de.halfbit.featured.sample.features; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.support.design.widget.CoordinatorLayout; 7 | import android.view.View; 8 | import android.widget.Toast; 9 | 10 | import de.halfbit.featured.sample.R; 11 | import de.halfbit.featured.sample.SampleFeature; 12 | import de.halfbit.featured.sample.util.Utils; 13 | 14 | public class ToastFeature extends SampleFeature implements View.OnClickListener { 15 | 16 | private View mButton; 17 | 18 | @Override 19 | protected void onCreate(@NonNull CoordinatorLayout parent, 20 | @Nullable Bundle savedInstanceState) { 21 | mButton = Utils.findAndShowView(parent, R.id.toast, this); 22 | } 23 | 24 | @Override protected void onDestroy() { 25 | mButton.setOnClickListener(null); 26 | } 27 | 28 | @Override public void onClick(View view) { 29 | Toast.makeText(view.getContext(), "Hello from ToastFeature", Toast.LENGTH_LONG).show(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /featured-sample/src/main/java/de/halfbit/featured/sample/util/Utils.java: -------------------------------------------------------------------------------- 1 | package de.halfbit.featured.sample.util; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | 8 | public class Utils { 9 | 10 | private Utils() { 11 | } 12 | 13 | public static T findAndShowView(@NonNull View parent, int id) { 14 | View view = parent.findViewById(id); 15 | if (view == null) { 16 | throw new IllegalStateException("Missing required view with id: " + id); 17 | } 18 | if (view.getVisibility() != View.VISIBLE) { 19 | view.setVisibility(View.VISIBLE); 20 | } 21 | //noinspection unchecked 22 | return (T) view; 23 | } 24 | 25 | public static T findAndShowView(@NonNull View parent, int id, 26 | @NonNull View.OnClickListener listener) { 27 | View view = findAndShowView(parent, id); 28 | view.setOnClickListener(listener); 29 | //noinspection unchecked 30 | return (T) view; 31 | } 32 | 33 | public static AppCompatActivity getActivity(Context context) { 34 | if (!(context instanceof AppCompatActivity)) { 35 | throw new IllegalArgumentException("Context is not an activity"); 36 | } 37 | return (AppCompatActivity) context; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /featured-sample/src/main/java/de/halfbit/featured/sample/features/SnackbarFeature.java: -------------------------------------------------------------------------------- 1 | package de.halfbit.featured.sample.features; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.support.design.widget.CoordinatorLayout; 7 | import android.support.design.widget.Snackbar; 8 | import android.view.View; 9 | 10 | import de.halfbit.featured.sample.R; 11 | import de.halfbit.featured.sample.SampleFeature; 12 | import de.halfbit.featured.sample.util.Utils; 13 | 14 | public class SnackbarFeature extends SampleFeature implements View.OnClickListener { 15 | 16 | private CoordinatorLayout mParent; 17 | private View mButton; 18 | 19 | @Override 20 | protected void onCreate(@NonNull CoordinatorLayout parent, 21 | @Nullable Bundle savedInstanceState) { 22 | mParent = parent; 23 | mButton = Utils.findAndShowView(mParent, R.id.snackbar, this); 24 | } 25 | 26 | @Override protected void onFabClicked() { 27 | Snackbar.make(mParent, 28 | "Hello from SnackBarFeature via FAB click", Snackbar.LENGTH_LONG).show(); 29 | } 30 | 31 | @Override protected void onDestroy() { 32 | mButton.setOnClickListener(null); 33 | } 34 | 35 | @Override public void onClick(View view) { 36 | Snackbar.make(mParent, "Hello from SnackBarFeature", Snackbar.LENGTH_LONG).show(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /featured-sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | 6 | compileOptions { 7 | sourceCompatibility = rootProject.ext.sourceCompatibilityVersion 8 | targetCompatibility = rootProject.ext.targetCompatibilityVersion 9 | } 10 | 11 | defaultConfig { 12 | applicationId 'de.halfbit.featured' 13 | minSdkVersion rootProject.ext.minSdkVersion 14 | targetSdkVersion rootProject.ext.targetSdkVersion 15 | versionCode 5 16 | versionName '0.2.0' 17 | } 18 | 19 | buildTypes { 20 | debug { 21 | minifyEnabled true 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | annotationProcessor project(':featured-compiler') 28 | implementation project(':featured') 29 | implementation project(':featured-sample-library') 30 | 31 | implementation deps.supportv4 32 | implementation deps.supportAppCompat 33 | implementation deps.supportDesign 34 | 35 | testImplementation deps.junit 36 | testImplementation deps.truth 37 | testImplementation deps.robolectric 38 | } 39 | 40 | afterEvaluate { 41 | tasks.withType(com.android.build.gradle.internal.tasks.AndroidTestTask) { task -> 42 | task.doFirst { 43 | logging.level = LogLevel.INFO 44 | } 45 | task.doLast { 46 | logging.level = LogLevel.LIFECYCLE 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /featured-compiler/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'checkstyle' 2 | apply plugin: 'java' 3 | 4 | sourceCompatibility = rootProject.ext.sourceCompatibilityVersion 5 | targetCompatibility = rootProject.ext.targetCompatibilityVersion 6 | 7 | def logger = new com.android.build.gradle.internal.LoggerWrapper(project.logger) 8 | def sdkHandler = new com.android.build.gradle.internal.SdkHandler(project, logger) 9 | for (File file : sdkHandler.sdkLoader.repositories) { 10 | repositories.maven { 11 | url = file.toURI() 12 | } 13 | } 14 | 15 | dependencies { 16 | compileOnly deps.android 17 | 18 | implementation project(':featured-annotations') 19 | implementation deps.autoservice 20 | implementation deps.autocommon 21 | implementation deps.javapoet 22 | implementation deps.jetbrainsAnnotations 23 | 24 | testImplementation deps.android 25 | testImplementation deps.junit 26 | testImplementation deps.truth 27 | testImplementation deps.robolectric 28 | // TODO: Unfortunately we can't use deps.supportv4 here as the Java plugin does not support AAR dependencies 29 | testImplementation 'com.google.android:support-v4:r7' 30 | testImplementation deps.compiletesting 31 | testImplementation files(org.gradle.internal.jvm.Jvm.current().getToolsJar()) 32 | } 33 | 34 | checkstyle { 35 | configFile rootProject.file('checkstyle.xml') 36 | showViolations true 37 | //Remove this when tests are less verbose, i.e. using JavaPoet 38 | sourceSets = [sourceSets.main] 39 | } 40 | 41 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 42 | -------------------------------------------------------------------------------- /docs/featured-design.md: -------------------------------------------------------------------------------- 1 | # Featured design 2 | Here is some rules and implementaiton details helping you to become familiar with the library and write cleaner code. 3 | - Features must not access other features directly but shall always interoperate through feature callbacks. By following this rule you will be rewarded with simple, maintainable and testable code later on. 4 | - You can define as many event callbacks as you need and they can have as many parameters as you need. 5 | - Generated feature host class will contain a `dispatchOn()` method for each feature's `on()` method. 6 | - To dispatch an event to all features you just need to call its corresponding `dispatchOn()`. 7 | - It is allowed to call a `dispatchOn()` method from a feature's `on()` callback. Feature host will make sure that currently running dispatch loop finishes and current event gets dispatched to all features before the new event gets dispatched. 8 | - This make event dispatching to be asynchronous. It means you cannot assume that a `dispatchOn()` finishes, corresponding event has been delivered to all features. Actual event dispatching can happen also later in time. If you want to be notified after an event has been dispatched, you need to use `@FeatureEvent(dispatchCompleted = true)` and provide corresponding `OnDispatchCompleted` callback in `dispatchOn()` method. Provided callback will be notified after event dispatching finishes. 9 | - Current implementation is intended to be used in MainThread. This is the only thread strategy implemented at this time. 10 | - Featured is being actively developed and new library features are to be expected. 11 | -------------------------------------------------------------------------------- /featured-compiler/src/test/java/de/halfbit/featured/FeatureHost.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package de.halfbit.featured; 17 | 18 | import org.jetbrains.annotations.NotNull; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | // Dummy class for tests to compile 22 | public abstract class FeatureHost { 23 | 24 | public interface OnDispatchCompleted { 25 | void onDispatchCompleted(); 26 | } 27 | 28 | protected static abstract class Event { 29 | @Nullable 30 | protected OnDispatchCompleted mOnDispatchCompleted; 31 | 32 | protected abstract void dispatch(@NotNull Feature feature); 33 | } 34 | 35 | public FeatureHost(@NotNull C context) { 36 | throw new RuntimeException("Stub!"); 37 | } 38 | 39 | protected void dispatch(Event event) { 40 | throw new RuntimeException("Stub!"); 41 | } 42 | 43 | protected void addFeature(Feature feature, @Nullable String featureName) { 44 | throw new RuntimeException("Stub!"); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /featured-sample/src/main/java/de/halfbit/featured/sample/features/LoggerFeature.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package de.halfbit.featured.sample.features; 17 | 18 | import android.os.Bundle; 19 | import android.support.annotation.NonNull; 20 | import android.support.annotation.Nullable; 21 | import android.support.design.widget.CoordinatorLayout; 22 | import android.util.Log; 23 | 24 | import de.halfbit.featured.sample.SampleFeature; 25 | 26 | public class LoggerFeature extends SampleFeature { 27 | 28 | private static final String TAG = "featured-sample-logger"; 29 | 30 | @Override 31 | protected void onCreate(@NonNull CoordinatorLayout parent, 32 | @Nullable Bundle savedInstanceState) { 33 | Log.d(TAG, "onCreate"); 34 | } 35 | 36 | @Override protected void onStart() { 37 | Log.d(TAG, "onStart"); 38 | } 39 | 40 | @Override protected void onStop() { 41 | Log.d(TAG, "onStop"); 42 | } 43 | 44 | @Override protected void onDestroy() { 45 | Log.d(TAG, "onDestroy"); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /featured-annotations/src/main/java/de/halfbit/featured/FeatureEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package de.halfbit.featured; 17 | 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.Target; 20 | 21 | import static java.lang.annotation.ElementType.METHOD; 22 | import static java.lang.annotation.RetentionPolicy.CLASS; 23 | 24 | /** 25 | * Method annotation to be used in a base feature as a feature callbacks marker. For each 26 | * class with annotated methods the library will generate a custom feature host class 27 | * with dispatch methods corresponding to annotated feature callbacks. See {@code Feature} 28 | * and {@code FeatureHost} for more detailed information. 29 | * 30 | * @author sergej shafarenka 31 | */ 32 | @Retention(CLASS) @Target(METHOD) 33 | public @interface FeatureEvent { 34 | 35 | /** 36 | * If set to true, generated class will have a {@code FeatureHost.OnDispatchCompleted} 37 | * a the last parameter of corresponding generated dispatch-method. 38 | * 39 | * @return the flag defining whether {@code FeatureHost.OnDispatchCompleted} shall be 40 | * added the last feature callback parameter to corresponding generated dispatch-method. 41 | */ 42 | boolean dispatchCompleted() default false; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /featured-sample/src/main/java/de/halfbit/featured/sample/SampleFeature.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package de.halfbit.featured.sample; 17 | 18 | import android.app.Activity; 19 | import android.os.Bundle; 20 | import android.support.annotation.NonNull; 21 | import android.support.annotation.Nullable; 22 | import android.support.design.widget.CoordinatorLayout; 23 | 24 | import de.halfbit.featured.Feature; 25 | import de.halfbit.featured.FeatureEvent; 26 | 27 | /** 28 | * Basis class for all features. It declares all events every feature can receive. 29 | * Featured library will generate a SampleFeatureHost to be used in your activity. 30 | */ 31 | public class SampleFeature extends Feature { 32 | 33 | @FeatureEvent 34 | protected void onCreate(@NonNull CoordinatorLayout parent, 35 | @Nullable Bundle savedInstanceState) { 36 | // nop 37 | } 38 | 39 | @FeatureEvent 40 | protected void onStart() { 41 | // nop 42 | } 43 | 44 | @FeatureEvent 45 | protected void onFabClicked() { 46 | // nop 47 | } 48 | 49 | @FeatureEvent 50 | protected void onStop() { 51 | // nop 52 | } 53 | 54 | @FeatureEvent 55 | protected void onDestroy() { 56 | // nop 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /featured-compiler/src/main/java/de/halfbit/featured/compiler/model/ParameterNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package de.halfbit.featured.compiler.model; 17 | 18 | import com.squareup.javapoet.TypeName; 19 | 20 | public class ParameterNode { 21 | 22 | private final MethodNode mMethodNode; 23 | private String mName; 24 | private TypeName mType; 25 | private boolean mIsDispatchCompleted; 26 | 27 | public ParameterNode(MethodNode methodNode) { 28 | mMethodNode = methodNode; 29 | } 30 | 31 | public String getName() { 32 | return mName; 33 | } 34 | 35 | public ParameterNode setName(String name) { 36 | mName = name; 37 | return this; 38 | } 39 | 40 | public TypeName getType() { 41 | return mType; 42 | } 43 | 44 | public ParameterNode setType(TypeName type) { 45 | mType = type; 46 | return this; 47 | } 48 | 49 | public boolean isDispatchCompleted() { 50 | return mIsDispatchCompleted; 51 | } 52 | 53 | public ParameterNode setDispatchCompleted(boolean dispatchCompleted) { 54 | mIsDispatchCompleted = dispatchCompleted; 55 | return this; 56 | } 57 | 58 | public void accept(ModelNodeVisitor visitor) { 59 | visitor.onParameter(this); 60 | } 61 | 62 | public MethodNode getParent() { 63 | return mMethodNode; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /featured-compiler/src/main/java/de/halfbit/featured/compiler/model/MethodNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package de.halfbit.featured.compiler.model; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import javax.lang.model.element.ExecutableElement; 22 | 23 | import de.halfbit.featured.FeatureEvent; 24 | 25 | public class MethodNode { 26 | 27 | private final ExecutableElement mElement; 28 | private final FeatureNode mParent; 29 | private List mParameterNodes; 30 | 31 | public MethodNode(ExecutableElement element, FeatureNode parent) { 32 | mElement = element; 33 | mParent = parent; 34 | } 35 | 36 | public void accept(ModelNodeVisitor visitor) { 37 | visitor.onMethodEnter(this); 38 | if (mParameterNodes != null) { 39 | for (ParameterNode parameterNode : mParameterNodes) { 40 | parameterNode.accept(visitor); 41 | } 42 | } 43 | visitor.onMethodExit(this); 44 | } 45 | 46 | public ExecutableElement getElement() { 47 | return mElement; 48 | } 49 | 50 | public FeatureNode getParent() { 51 | return mParent; 52 | } 53 | 54 | public boolean hasDispatchCompletedParameter() { 55 | FeatureEvent ann = mElement.getAnnotation(FeatureEvent.class); 56 | return ann != null && ann.dispatchCompleted(); 57 | } 58 | 59 | public void addParameter(ParameterNode parameter) { 60 | if (mParameterNodes == null) { 61 | mParameterNodes = new ArrayList<>(6); 62 | } 63 | mParameterNodes.add(parameter); 64 | } 65 | 66 | public boolean hasParameters() { 67 | return mParameterNodes != null; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /featured-sample/src/main/java/de/halfbit/featured/sample/SampleActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package de.halfbit.featured.sample; 17 | 18 | import android.os.Bundle; 19 | import android.support.design.widget.CoordinatorLayout; 20 | import android.support.v7.app.AppCompatActivity; 21 | 22 | import de.halfbit.featured.sample.features.FabFeature; 23 | import de.halfbit.featured.sample.features.LoggerFeature; 24 | import de.halfbit.featured.sample.features.SnackbarFeature; 25 | import de.halfbit.featured.sample.features.ToastFeature; 26 | import de.halfbit.featured.sample.features.ToolbarFeature; 27 | 28 | public class SampleActivity extends AppCompatActivity { 29 | 30 | private SampleFeatureHost mFeatureHost; 31 | 32 | @Override protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_sample); 35 | 36 | // try to comment out some features and see what happens 37 | mFeatureHost = new SampleFeatureHost(this) 38 | .with(new LoggerFeature()) 39 | .with(new ToolbarFeature()) 40 | .with(new SnackbarFeature()) 41 | .with(new ToastFeature()) 42 | .with(new FabFeature()); 43 | 44 | CoordinatorLayout parent = (CoordinatorLayout) 45 | findViewById(R.id.coordinatorLayout); 46 | mFeatureHost.dispatchOnCreate(parent, savedInstanceState); 47 | } 48 | 49 | @Override protected void onStart() { 50 | super.onStart(); 51 | mFeatureHost.dispatchOnStart(); 52 | } 53 | 54 | @Override protected void onStop() { 55 | mFeatureHost.dispatchOnStop(); 56 | super.onStop(); 57 | } 58 | 59 | @Override protected void onDestroy() { 60 | mFeatureHost.dispatchOnDestroy(); 61 | super.onDestroy(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is **@deprecated** in favor of [Knot](https://github.com/beworker/knot) library. It offers the same capability of decomposing complex application logic into smaller features, but does it in a reactive way using modern predictable state container pattern. 2 | 3 | --- 4 | 5 | [![Build Status](https://travis-ci.org/beworker/featured.svg?branch=master)](https://travis-ci.org/beworker/featured) 6 | [![Maven Central](http://img.shields.io/maven-central/v/de.halfbit/featured.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22de.halfbit%22%20a%3A%22featured%22) 7 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) 8 | 9 | 10 | # Featured 11 | This library will help you to split activity or fragment code into truly decoupled, testable and maintainable features. 12 | 13 | Features are small units sharing same lifecycle. They are hosted by a `FeatureHost` class which, in turn, resides in an activity or a fragment. Features communicate to each other through the feature host by means of events as shown in the diagram below. 14 | 15 | ![diagram][1] 16 | 17 | # Documentation 18 | 19 | - [Quick Start][2] 20 | - [Featured Design][3] 21 | - [Migrate to version 0.1.0 or higher][4] 22 | 23 | # Use with Gradle 24 | 25 | Add this to you project-level `build.gradle`: 26 | 27 | ```groovy 28 | buildscript { 29 | repositories { 30 | mavenCentral() 31 | } 32 | } 33 | ``` 34 | 35 | Add this to your module-level `build.gradle`: 36 | 37 | ```groovy 38 | dependencies { 39 | annotationProcessor 'de.halfbit:featured-compiler:' 40 | compile 'de.halfbit:featured:' 41 | } 42 | ``` 43 | 44 | [![Maven Central](http://img.shields.io/maven-central/v/de.halfbit/featured.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22de.halfbit%22%20a%3A%22featured%22) 45 | 46 | # License 47 | ``` 48 | Copyright 2016-2019 Sergej Shafarenka, www.halfbit.de 49 | 50 | Licensed under the Apache License, Version 2.0 (the "License"); 51 | you may not use this file except in compliance with the License. 52 | You may obtain a copy of the License at 53 | 54 | http://www.apache.org/licenses/LICENSE-2.0 55 | 56 | Unless required by applicable law or agreed to in writing, software 57 | distributed under the License is distributed on an "AS IS" BASIS, 58 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 59 | See the License for the specific language governing permissions and 60 | limitations under the License. 61 | ``` 62 | 63 | [1]: docs/images/diagram.png 64 | [2]: docs/quick-start.md 65 | [3]: docs/featured-design.md 66 | [4]: docs/migrate-to-0.1.0.md 67 | -------------------------------------------------------------------------------- /featured-sample/src/test/java/de/halfbit/featured/sample/library/FeatureImplementationTest.java: -------------------------------------------------------------------------------- 1 | package de.halfbit.featured.sample.library; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.robolectric.RobolectricTestRunner; 6 | 7 | import static com.google.common.truth.Truth.assertThat; 8 | 9 | @RunWith(RobolectricTestRunner.class) 10 | public class FeatureImplementationTest { 11 | 12 | @Test 13 | public void testEventDispatchingWithInheritance() { 14 | 15 | TestLifecycleFeature testLifecycleFeature = new TestLifecycleFeature(); 16 | TestSampleLibraryFeature testSampleLibraryFeature = new TestSampleLibraryFeature(); 17 | 18 | SampleLibraryFeatureHost host = new SampleLibraryFeatureHost(new SampleLibraryActivity()) 19 | .with(testLifecycleFeature) 20 | .with(testSampleLibraryFeature); 21 | 22 | host.dispatchOnCreate(); 23 | host.dispatchOnStart(); 24 | host.dispatchOnDataLoaded(); 25 | host.dispatchOnUiShown(); 26 | host.dispatchOnStop(); 27 | host.dispatchOnDestroy(); 28 | 29 | testLifecycleFeature.assertEvents( 30 | "onCreate", "onStart", "onStop", "onDestroy"); 31 | 32 | testSampleLibraryFeature.assertEvents( 33 | "onCreate", "onStart", "onDataLoaded", "onUiShown", "onStop", "onDestroy"); 34 | } 35 | 36 | @Test 37 | public void testNamedFeatures() { 38 | 39 | TestLifecycleFeature testLifecycleFeature1 = new TestLifecycleFeature(); 40 | TestLifecycleFeature testLifecycleFeature2 = new TestLifecycleFeature(); 41 | 42 | SampleLibraryFeatureHost host = new SampleLibraryFeatureHost(new SampleLibraryActivity()) 43 | .with(testLifecycleFeature1) 44 | .with(testLifecycleFeature1, "feature1") 45 | .with(testLifecycleFeature2, "feature2"); 46 | 47 | host.dispatchOnCreate(); 48 | host.dispatchOnDestroy(); 49 | 50 | TestLifecycleFeature feature1a = host.getFeature(TestLifecycleFeature.class); 51 | assertThat(feature1a).isSameAs(testLifecycleFeature1); 52 | 53 | TestLifecycleFeature feature1b = host.getFeature(TestLifecycleFeature.class, "feature1"); 54 | assertThat(feature1b).isSameAs(testLifecycleFeature1); 55 | 56 | TestLifecycleFeature feature2b = host.getFeature(TestLifecycleFeature.class, "feature2"); 57 | assertThat(feature2b).isSameAs(testLifecycleFeature2); 58 | 59 | testLifecycleFeature1.assertEvents("onCreate", "onCreate", "onDestroy", "onDestroy"); 60 | testLifecycleFeature2.assertEvents("onCreate", "onDestroy"); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /featured-sample/src/main/res/layout/activity_sample.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 14 | 24 | 25 | 26 | 27 | 33 | 34 |