├── .cirrus.yml ├── .github └── dependabot.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle ├── checkstyle.xml ├── examples ├── basic │ ├── .gitignore │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── soundcloud │ │ │ └── lightcycle │ │ │ └── sample │ │ │ └── basic │ │ │ ├── ActivityLogger.java │ │ │ └── SampleActivity.java │ │ └── res │ │ ├── layout │ │ └── activity_sample.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml └── real-world │ ├── .gitignore │ ├── build.gradle │ └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── soundcloud │ │ │ └── lightcycle │ │ │ └── sample │ │ │ └── real_world │ │ │ ├── ApplicationModule.java │ │ │ ├── SampleApp.java │ │ │ ├── home │ │ │ ├── DescriptionPresenter.java │ │ │ ├── HeaderPresenter.java │ │ │ ├── HomeActivity.java │ │ │ ├── HomePresenter.java │ │ │ └── HomeView.java │ │ │ ├── license │ │ │ ├── LicenseFragment.java │ │ │ ├── LicensePresenter.java │ │ │ └── LicenseView.java │ │ │ ├── tracker │ │ │ ├── Screen.java │ │ │ ├── ScreenTracker.java │ │ │ └── TrackingOperations.java │ │ │ └── utils │ │ │ └── DateProvider.java │ └── res │ │ ├── layout │ │ ├── activity_home.xml │ │ └── fragment_license.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── soundcloud │ └── lightcycle │ └── sample │ └── real_world │ ├── home │ └── HeaderPresenterTest.java │ ├── tracker │ └── ScreenTrackerTest.java │ └── utils │ └── DateProviderTest.java ├── gradle.properties ├── gradle ├── gradle-mvn-push.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lightcycle-api ├── build.gradle ├── gradle.properties └── src │ ├── main │ └── java │ │ └── com │ │ └── soundcloud │ │ └── lightcycle │ │ ├── ActivityLightCycle.java │ │ ├── DefaultActivityLightCycle.java │ │ ├── DefaultFragmentLightCycle.java │ │ ├── DefaultSupportFragmentLightCycle.java │ │ ├── FragmentLightCycle.java │ │ ├── LightCycle.java │ │ ├── LightCycleDispatcher.java │ │ ├── LightCycles.java │ │ └── SupportFragmentLightCycle.java │ └── test │ └── java │ └── com │ └── soundcloud │ └── lightcycle │ └── LightCyclesTest.java ├── lightcycle-integration-test ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── soundcloud │ │ │ └── lightcycle │ │ │ └── integration_test │ │ │ ├── ActivityLogger.java │ │ │ ├── AppCompatActivityLogger.java │ │ │ ├── BaseActivityLogger.java │ │ │ ├── FragmentLogger.java │ │ │ ├── SampleActivity.java │ │ │ ├── SampleAppCompatActivity.java │ │ │ ├── SampleFragment.java │ │ │ ├── SampleFragmentDispatcher.java │ │ │ ├── SampleSupportFragment.java │ │ │ ├── SampleSupportFragmentDispatcher.java │ │ │ ├── SupportFragmentLogger.java │ │ │ └── callback │ │ │ ├── ActivityLifecycleCallback.java │ │ │ └── FragmentLifecycleCallback.java │ └── res │ │ ├── layout │ │ ├── activity_sample.xml │ │ └── fragment_sample.xml │ │ ├── menu │ │ └── menu_search.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ ├── com │ └── soundcloud │ │ └── lightcycle │ │ └── integration_test │ │ ├── ActivityLoggerTest.java │ │ ├── AppCompatActivityLoggerTest.java │ │ ├── FragmentLoggerTest.java │ │ ├── SampleFragmentDispatcherTest.java │ │ ├── SampleSupportFragmentDispatcherTest.java │ │ └── SupportFragmentLoggerTest.java │ └── utils │ └── FragmentTestHelper.java ├── lightcycle-lib ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.txt └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── soundcloud │ │ └── lightcycle │ │ ├── ActivityLightCycleDispatcher.java │ │ ├── FragmentLightCycleDispatcher.java │ │ ├── LightCycleActivity.java │ │ ├── LightCycleAppCompatActivity.java │ │ ├── LightCycleFragment.java │ │ ├── LightCyclePreferenceFragmentCompat.java │ │ ├── LightCycleSupportDialogFragment.java │ │ ├── LightCycleSupportFragment.java │ │ ├── SupportFragmentLightCycleDispatcher.java │ │ └── util │ │ ├── LightCycleBinderHelper.java │ │ └── Preconditions.java │ └── test │ └── java │ └── com │ └── soundcloud │ └── lightcycle │ ├── ActivityLightCycleDispatcherTest.java │ ├── FragmentLightCycleDispatcherTest.java │ └── SupportFragmentLightCycleDispatcherTest.java ├── lightcycle-processor-tests ├── build.gradle └── src │ ├── main │ └── AndroidManifest.xml │ └── test │ └── java │ └── com │ └── soundcloud │ └── lightcycle │ ├── ActivityInjectionTest.java │ ├── AndroidXFragmentInjectionTest.java │ ├── FragmentInjectionTest.java │ ├── InvalidCaseTest.java │ └── ParameterizedDispatcherTest.java ├── lightcycle-processor ├── build.gradle ├── gradle.properties └── src │ └── main │ ├── java │ └── com │ │ └── soundcloud │ │ └── lightcycle │ │ ├── LightCycleBinder.java │ │ ├── LightCycleDispatcherKind.java │ │ └── LightCycleProcessor.java │ └── resources │ └── META-INF │ ├── gradle │ └── incremental.annotation.processors │ └── services │ └── javax.annotation.processing.Processor ├── release.sh └── settings.gradle /.cirrus.yml: -------------------------------------------------------------------------------- 1 | task: 2 | name: Check 3 | container: 4 | image: circleci/android:api-29 5 | check_script: ./gradlew check 6 | always: 7 | junit_result_artifacts: 8 | path: "**/test-results/**/*.xml" 9 | format: junit 10 | type: text/xml 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | commit-message: 8 | prefix: chore 9 | include: scope 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | target 4 | build 5 | .DS_Store 6 | .gradle 7 | local.properties 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | before_script: 4 | - "export JAVA_OPTS=-Xmx512m" 5 | 6 | android: 7 | components: 8 | - tools 9 | - platform-tools 10 | - tools # This duplication is intented to make travis accept license, please check https://github.com/travis-ci/docs-travis-ci-com/issues/779 11 | - build-tools-27.0.3 12 | - android-27 13 | - extra-android-m2repository 14 | licenses: 15 | - 'android-sdk-preview-license-52d11cd2' 16 | - 'android-sdk-license-.+' 17 | - 'google-gdk-license-.+' 18 | 19 | jdk: 20 | - oraclejdk8 21 | 22 | branches: 23 | except: 24 | - gh-pages 25 | 26 | notifications: 27 | email: false 28 | 29 | sudo: false 30 | 31 | cache: 32 | directories: 33 | - $HOME/.m2 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 / 2016-03-22 2 | 3 | Initial public release 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | If you would like to contribute code to LightCycle you can do so through GitHub by 5 | forking the repository and sending a pull request. 6 | 7 | When submitting code, please make every effort to follow existing conventions 8 | and style in order to keep the code as readable as possible. 9 | 10 | All pull requests will be validated by Travis-ci in any case and must pass before being merged. 11 | 12 | Checkstyle failures during compilation indicate errors in your style and will be displayed in the console output of the build (including in Travis-CI output), or can be viewed in the checkstyle-result.xml file. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LightCycle 2 | 3 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.soundcloud.lightcycle/lightcycle-lib/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.soundcloud.lightcycle/lightcycle-lib) [![Hex.pm](https://img.shields.io/hexpm/l/plug.svg)](http://www.apache.org/licenses/LICENSE-2.0) [![Platform](https://img.shields.io/badge/platform-android-green.svg)](http://developer.android.com/index.html) 4 | 5 | LightCycle is an Android library that helps break logic out of `Activity` and `Fragment` classes into small, self-contained components called LightCycles. 6 | 7 | Fields that are annotated `@LightCycle` and implement the LightCycle API within a `LightCycleActivity` or `LightCycleFragment` will be bound to that `Activity` or `Fragment` lifecycle. 8 | 9 | For more information please see the [website](http://soundcloud.github.io/lightcycle/). 10 | 11 | ## Examples 12 | 13 | - [basic](https://github.com/soundcloud/lightcycle/tree/master/examples/basic) 14 | - ["real world"](https://github.com/soundcloud/lightcycle/tree/master/examples/real-world) 15 | 16 | ## Build integration 17 | 18 | Gradle: 19 | 20 | ```gradle 21 | ext.lightCycleVersion= 22 | 23 | dependencies { 24 | compile "com.soundcloud.lightcycle:lightcycle-lib:$lightCycleVersion" 25 | annotationProcessor "com.soundcloud.lightcycle:lightcycle-processor:$lightCycleVersion" 26 | } 27 | ``` 28 | 29 | Or if you're using a version of the Android gradle plugin below `2.2.0` 30 | 31 | ```gradle 32 | buildscript { 33 | dependencies { 34 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 35 | } 36 | } 37 | 38 | apply plugin: 'com.neenbedankt.android-apt' 39 | 40 | ext.lightCycleVersion= 41 | 42 | dependencies { 43 | compile "com.soundcloud.lightcycle:lightcycle-lib:$lightCycleVersion" 44 | apt "com.soundcloud.lightcycle:lightcycle-processor:$lightCycleVersion" 45 | } 46 | ``` 47 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenLocal() 4 | google() 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.6.2' 9 | classpath 'com.netflix.nebula:gradle-extra-configurations-plugin:2.2.0' 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | mavenLocal() 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | ext { 22 | minSdkVersion = 21 23 | targetSdkVersion = 27 24 | compileSdkVersion = 29 25 | sourceCompatibilityVersion = JavaVersion.VERSION_1_8 26 | targetCompatibilityVersion = JavaVersion.VERSION_1_8 27 | } 28 | 29 | ext.deps = [ 30 | // Android 31 | android : "com.google.android:android:4.1.1.4", 32 | preference : "androidx.preference:preference:1.1.0", 33 | appcompat : "androidx.appcompat:appcompat:1.1.0", 34 | annotations : "androidx.annotation:annotation:1.1.0", 35 | 36 | // Processor 37 | javapoet : 'com.squareup:javapoet:1.6.1', 38 | 39 | // Test dependencies 40 | junit : 'junit:junit:4.12', 41 | mockito : 'org.mockito:mockito-all:1.10.19', 42 | compiletesting : 'com.google.testing.compile:compile-testing:0.18', 43 | roboelectric : 'org.robolectric:robolectric:4.3.1', 44 | equalsverifier : 'nl.jqno.equalsverifier:equalsverifier:2.1.8', 45 | fragment_testing : 'androidx.fragment:fragment-testing:1.2.0', 46 | androidx_junit : 'androidx.test.ext:junit:1.1.1' 47 | ] 48 | 49 | // Static analysis 50 | subprojects { project -> 51 | apply plugin: 'checkstyle' 52 | 53 | checkstyle { 54 | toolVersion = "7.2" 55 | configFile rootProject.file('checkstyle.xml') 56 | } 57 | 58 | task checkstyle(type: Checkstyle) { 59 | source 'src/main/java' 60 | ignoreFailures false 61 | showViolations true 62 | include '**/*.java' 63 | 64 | classpath = files() 65 | } 66 | } 67 | 68 | // configure Java projects 69 | [":lightcycle-api", ":lightcycle-processor"].each { name -> 70 | project(name) { 71 | 72 | apply plugin: 'provided-base' 73 | apply plugin: 'java' 74 | 75 | sourceCompatibility = rootProject.ext.sourceCompatibilityVersion 76 | targetCompatibility = rootProject.ext.targetCompatibilityVersion 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /examples/basic/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /examples/basic/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | dependencies { 7 | // replace with the current version of the Android plugin 8 | classpath 'com.android.tools.build:gradle:3.6.2' 9 | } 10 | } 11 | 12 | apply plugin: 'com.android.application' 13 | 14 | android { 15 | compileSdkVersion rootProject.ext.compileSdkVersion 16 | 17 | compileOptions { 18 | sourceCompatibility rootProject.ext.sourceCompatibilityVersion 19 | targetCompatibility rootProject.ext.targetCompatibilityVersion 20 | } 21 | 22 | defaultConfig { 23 | minSdkVersion rootProject.ext.minSdkVersion 24 | targetSdkVersion rootProject.ext.targetSdkVersion 25 | versionCode 1 26 | versionName "1.0" 27 | } 28 | 29 | } 30 | 31 | dependencies { 32 | annotationProcessor project(":lightcycle-processor") 33 | 34 | implementation project(":lightcycle-lib") 35 | implementation deps.appcompat 36 | } 37 | -------------------------------------------------------------------------------- /examples/basic/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/basic/src/main/java/com/soundcloud/lightcycle/sample/basic/ActivityLogger.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.basic; 2 | 3 | import com.soundcloud.lightcycle.DefaultActivityLightCycle; 4 | 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | 8 | import androidx.appcompat.app.AppCompatActivity; 9 | 10 | class ActivityLogger extends DefaultActivityLightCycle { 11 | 12 | private static final String TAG = "ACTIVITY_LOG"; 13 | 14 | @Override 15 | public void onCreate(AppCompatActivity activity, Bundle bundle) { 16 | Log.i(TAG, "Creating activity:" + activity); 17 | } 18 | 19 | @Override 20 | public void onStart(AppCompatActivity activity) { 21 | Log.i(TAG, "Starting activity:" + activity); 22 | } 23 | 24 | @Override 25 | public void onResume(AppCompatActivity activity) { 26 | Log.i(TAG, "Resuming activity:" + activity); 27 | } 28 | 29 | @Override 30 | public void onPause(AppCompatActivity activity) { 31 | Log.i(TAG, "Pausing activity:" + activity); 32 | } 33 | 34 | @Override 35 | public void onStop(AppCompatActivity activity) { 36 | Log.i(TAG, "Stopping activity:" + activity); 37 | } 38 | 39 | @Override 40 | public void onDestroy(AppCompatActivity activity) { 41 | Log.i(TAG, "Destroying activity:" + activity); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/basic/src/main/java/com/soundcloud/lightcycle/sample/basic/SampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.basic; 2 | 3 | import com.soundcloud.lightcycle.LightCycle; 4 | import com.soundcloud.lightcycle.LightCycleAppCompatActivity; 5 | 6 | public class SampleActivity extends LightCycleAppCompatActivity { 7 | @LightCycle ActivityLogger activityLogger = new ActivityLogger(); 8 | 9 | @Override 10 | protected void setActivityContentView() { 11 | setContentView(R.layout.activity_sample); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/basic/src/main/res/layout/activity_sample.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/basic/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soundcloud/lightcycle/35207504e3785d7c162189f51d475ba75fbd81cb/examples/basic/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/basic/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soundcloud/lightcycle/35207504e3785d7c162189f51d475ba75fbd81cb/examples/basic/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/basic/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soundcloud/lightcycle/35207504e3785d7c162189f51d475ba75fbd81cb/examples/basic/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/basic/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soundcloud/lightcycle/35207504e3785d7c162189f51d475ba75fbd81cb/examples/basic/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/basic/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soundcloud/lightcycle/35207504e3785d7c162189f51d475ba75fbd81cb/examples/basic/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/basic/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffffff 4 | #bbbbbb 5 | @color/soundcloud 6 | #ff5500 7 | 8 | -------------------------------------------------------------------------------- /examples/basic/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | LightCycle Basic example. 3 | 4 | -------------------------------------------------------------------------------- /examples/basic/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/real-world/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /examples/real-world/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | dependencies { 7 | // replace with the current version of the Android plugin 8 | classpath 'com.android.tools.build:gradle:3.6.2' 9 | } 10 | } 11 | 12 | apply plugin: 'com.android.application' 13 | 14 | android { 15 | compileSdkVersion rootProject.ext.compileSdkVersion 16 | 17 | compileOptions { 18 | sourceCompatibility rootProject.ext.sourceCompatibilityVersion 19 | targetCompatibility rootProject.ext.targetCompatibilityVersion 20 | } 21 | 22 | defaultConfig { 23 | minSdkVersion rootProject.ext.minSdkVersion 24 | targetSdkVersion rootProject.ext.targetSdkVersion 25 | versionCode 1 26 | versionName "1.0" 27 | } 28 | } 29 | 30 | dependencies { 31 | annotationProcessor project(":lightcycle-processor") 32 | implementation project(":lightcycle-lib") 33 | 34 | annotationProcessor "com.squareup.dagger:dagger-compiler:1.2.2" 35 | implementation "com.squareup.dagger:dagger:1.2.2" 36 | 37 | implementation deps.appcompat 38 | 39 | testImplementation deps.junit 40 | testImplementation deps.mockito 41 | } 42 | -------------------------------------------------------------------------------- /examples/real-world/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/ApplicationModule.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world; 2 | 3 | import com.soundcloud.lightcycle.sample.real_world.home.HomeActivity; 4 | import com.soundcloud.lightcycle.sample.real_world.license.LicenseFragment; 5 | import dagger.Module; 6 | import dagger.Provides; 7 | 8 | import java.util.Calendar; 9 | 10 | @Module(injects = {HomeActivity.class, LicenseFragment.class}) 11 | class ApplicationModule { 12 | 13 | @Provides Calendar provideCalendar() { 14 | return Calendar.getInstance(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/SampleApp.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world; 2 | 3 | import dagger.ObjectGraph; 4 | 5 | import android.app.Application; 6 | 7 | public class SampleApp extends Application { 8 | 9 | private static SampleApp instance; 10 | 11 | protected ObjectGraph objectGraph; 12 | 13 | @Override 14 | public void onCreate() { 15 | super.onCreate(); 16 | objectGraph = ObjectGraph.create(new ApplicationModule()); 17 | instance = this; 18 | } 19 | 20 | public static ObjectGraph getObjectGraph() { 21 | if (instance == null || instance.objectGraph == null) { 22 | throw new IllegalStateException("Cannot access the app graph before the application has been created"); 23 | } 24 | return instance.objectGraph; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/home/DescriptionPresenter.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.home; 2 | 3 | import com.soundcloud.lightcycle.DefaultActivityLightCycle; 4 | 5 | import android.os.Bundle; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | import javax.inject.Inject; 10 | 11 | class DescriptionPresenter extends DefaultActivityLightCycle { 12 | 13 | @Inject 14 | public DescriptionPresenter() { 15 | } 16 | 17 | @Override 18 | public void onCreate(HomeView homeView, @Nullable Bundle bundle) { 19 | homeView.showDescription("LightCycle", "https://github.com/soundcloud/lightcycle"); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/home/HeaderPresenter.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.home; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import com.soundcloud.lightcycle.DefaultActivityLightCycle; 8 | import com.soundcloud.lightcycle.sample.real_world.R; 9 | import com.soundcloud.lightcycle.sample.real_world.utils.DateProvider; 10 | 11 | import javax.inject.Inject; 12 | 13 | class HeaderPresenter extends DefaultActivityLightCycle { 14 | private final DateProvider dateProvider; 15 | 16 | @Inject 17 | public HeaderPresenter(DateProvider dateProvider) { 18 | this.dateProvider = dateProvider; 19 | } 20 | 21 | @Override 22 | public void onCreate(HomeView homeView, @Nullable Bundle bundle) { 23 | if (dateProvider.isMorning()) { 24 | homeView.sayHello(R.string.good_morning); 25 | } else { 26 | homeView.sayHello(R.string.hello); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/home/HomeActivity.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.home; 2 | 3 | import com.soundcloud.lightcycle.LightCycle; 4 | import com.soundcloud.lightcycle.LightCycleAppCompatActivity; 5 | import com.soundcloud.lightcycle.sample.real_world.R; 6 | import com.soundcloud.lightcycle.sample.real_world.SampleApp; 7 | import com.soundcloud.lightcycle.sample.real_world.tracker.ScreenTracker; 8 | 9 | import android.widget.TextView; 10 | 11 | import androidx.annotation.StringRes; 12 | 13 | import javax.inject.Inject; 14 | 15 | public class HomeActivity extends LightCycleAppCompatActivity implements HomeView { 16 | @Inject @LightCycle ScreenTracker screenTracker; 17 | @Inject @LightCycle HomePresenter headerPresenter; 18 | 19 | public HomeActivity() { 20 | SampleApp.getObjectGraph().inject(this); 21 | } 22 | 23 | @Override 24 | public String getScreenName() { 25 | return "HomeScreen"; 26 | } 27 | 28 | @Override 29 | protected void setActivityContentView() { 30 | setContentView(R.layout.activity_home); 31 | } 32 | 33 | @Override 34 | public void sayHello(@StringRes int message) { 35 | ((TextView) findViewById(R.id.hello)).setText(message); 36 | } 37 | 38 | @Override 39 | public void showDescription(String title, String message) { 40 | ((TextView) findViewById(R.id.title)).setText(title); 41 | ((TextView) findViewById(R.id.description)).setText(message); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/home/HomePresenter.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.home; 2 | 3 | import com.soundcloud.lightcycle.ActivityLightCycleDispatcher; 4 | import com.soundcloud.lightcycle.LightCycle; 5 | 6 | import javax.inject.Inject; 7 | 8 | class HomePresenter extends ActivityLightCycleDispatcher { 9 | @LightCycle final HeaderPresenter headerPresenter; 10 | @LightCycle final DescriptionPresenter descriptionPresenter; 11 | 12 | @Inject 13 | HomePresenter(HeaderPresenter headerPresenter, DescriptionPresenter descriptionPresenter) { 14 | this.headerPresenter = headerPresenter; 15 | this.descriptionPresenter = descriptionPresenter; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/home/HomeView.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.home; 2 | 3 | import androidx.annotation.StringRes; 4 | 5 | import com.soundcloud.lightcycle.sample.real_world.tracker.Screen; 6 | 7 | interface HomeView extends Screen { 8 | 9 | void sayHello(@StringRes int message); 10 | 11 | void showDescription(String title, String message); 12 | } 13 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/license/LicenseFragment.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.license; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | 9 | import com.soundcloud.lightcycle.LightCycle; 10 | import com.soundcloud.lightcycle.LightCycleSupportFragment; 11 | import com.soundcloud.lightcycle.sample.real_world.R; 12 | import com.soundcloud.lightcycle.sample.real_world.SampleApp; 13 | 14 | import javax.inject.Inject; 15 | 16 | public class LicenseFragment extends LightCycleSupportFragment implements LicenseView { 17 | 18 | @Inject @LightCycle LicensePresenter licensePresenter; 19 | 20 | public LicenseFragment() { 21 | SampleApp.getObjectGraph().inject(this); 22 | } 23 | 24 | @Override 25 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 26 | return inflater.inflate(R.layout.fragment_license, container, false); 27 | } 28 | 29 | @Override 30 | public void showLicense(String text) { 31 | ((TextView) getView().findViewById(R.id.license)).setText(text); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/license/LicensePresenter.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.license; 2 | 3 | import com.soundcloud.lightcycle.DefaultSupportFragmentLightCycle; 4 | 5 | import android.os.Bundle; 6 | import android.view.View; 7 | 8 | import javax.inject.Inject; 9 | 10 | class LicensePresenter extends DefaultSupportFragmentLightCycle { 11 | 12 | @Inject 13 | public LicensePresenter() { 14 | } 15 | 16 | @Override 17 | public void onViewCreated(LicenseView licenseView, View view, Bundle savedInstanceState) { 18 | licenseView.showLicense("Apache License 2.0"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/license/LicenseView.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.license; 2 | 3 | interface LicenseView { 4 | void showLicense(String text); 5 | } 6 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/tracker/Screen.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.tracker; 2 | 3 | public interface Screen { 4 | String getScreenName(); 5 | } 6 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/tracker/ScreenTracker.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.tracker; 2 | 3 | import com.soundcloud.lightcycle.DefaultActivityLightCycle; 4 | 5 | import javax.inject.Inject; 6 | 7 | public class ScreenTracker extends DefaultActivityLightCycle { 8 | 9 | private final TrackingOperations operations; 10 | 11 | @Inject 12 | public ScreenTracker(TrackingOperations operations) { 13 | this.operations = operations; 14 | } 15 | 16 | @Override 17 | public void onResume(Screen screen) { 18 | operations.trackScreen(screen); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/tracker/TrackingOperations.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.tracker; 2 | 3 | import javax.inject.Inject; 4 | 5 | public class TrackingOperations { 6 | @Inject 7 | public TrackingOperations() { 8 | } 9 | 10 | public void trackScreen(Screen screen) { 11 | System.out.println("Tracking screen:" + screen.getScreenName()); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /examples/real-world/src/main/java/com/soundcloud/lightcycle/sample/real_world/utils/DateProvider.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.utils; 2 | 3 | import androidx.annotation.VisibleForTesting; 4 | 5 | import javax.inject.Inject; 6 | import java.util.Calendar; 7 | 8 | public class DateProvider { 9 | 10 | private final Calendar calendar; 11 | 12 | @Inject 13 | public DateProvider(Calendar calendar) { 14 | this.calendar = calendar; 15 | } 16 | 17 | @VisibleForTesting 18 | public DateProvider() { 19 | this.calendar = null; 20 | } 21 | 22 | 23 | public long currentTimeMillis() { 24 | return System.currentTimeMillis(); 25 | } 26 | 27 | public boolean isMorning() { 28 | final int hourOfDay = calendar.get(Calendar.HOUR_OF_DAY); 29 | return hourOfDay >= 0 && hourOfDay < 12; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/real-world/src/main/res/layout/activity_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 23 | 24 | 31 | 32 | 38 | 39 | -------------------------------------------------------------------------------- /examples/real-world/src/main/res/layout/fragment_license.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/real-world/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soundcloud/lightcycle/35207504e3785d7c162189f51d475ba75fbd81cb/examples/real-world/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/real-world/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soundcloud/lightcycle/35207504e3785d7c162189f51d475ba75fbd81cb/examples/real-world/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/real-world/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soundcloud/lightcycle/35207504e3785d7c162189f51d475ba75fbd81cb/examples/real-world/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/real-world/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soundcloud/lightcycle/35207504e3785d7c162189f51d475ba75fbd81cb/examples/real-world/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/real-world/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soundcloud/lightcycle/35207504e3785d7c162189f51d475ba75fbd81cb/examples/real-world/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/real-world/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffffff 4 | #bbbbbb 5 | @color/soundcloud 6 | #ff5500 7 | 8 | -------------------------------------------------------------------------------- /examples/real-world/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | LightCycle \"real world\" example. 3 | Good morning 4 | Hello 5 | 6 | -------------------------------------------------------------------------------- /examples/real-world/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/real-world/src/test/java/com/soundcloud/lightcycle/sample/real_world/home/HeaderPresenterTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.home; 2 | 3 | import static org.mockito.Mockito.verify; 4 | 5 | import com.soundcloud.lightcycle.sample.real_world.R; 6 | import com.soundcloud.lightcycle.sample.real_world.utils.DateProvider; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.Mock; 10 | import org.mockito.runners.MockitoJUnitRunner; 11 | 12 | import android.os.Bundle; 13 | 14 | @RunWith(MockitoJUnitRunner.class) 15 | public class HeaderPresenterTest { 16 | @Mock private HomeView view; 17 | 18 | @Test 19 | public void sayHelloWhenNotMorning() { 20 | HeaderPresenter presenter = new HeaderPresenter(new DateProvider() { 21 | @Override 22 | public boolean isMorning() { 23 | return false; 24 | } 25 | }); 26 | 27 | presenter.onCreate(view, Bundle.EMPTY); 28 | 29 | verify(view).sayHello(R.string.hello); 30 | } 31 | 32 | @Test 33 | public void sayGoodMorning() { 34 | HeaderPresenter presenter = new HeaderPresenter(new DateProvider() { 35 | @Override 36 | public boolean isMorning() { 37 | return true; 38 | } 39 | }); 40 | 41 | presenter.onCreate(view, Bundle.EMPTY); 42 | 43 | verify(view).sayHello(R.string.good_morning); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/real-world/src/test/java/com/soundcloud/lightcycle/sample/real_world/tracker/ScreenTrackerTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.tracker; 2 | 3 | import static org.mockito.Mockito.verify; 4 | 5 | import com.soundcloud.lightcycle.sample.real_world.home.HomeActivity; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.Mock; 10 | import org.mockito.runners.MockitoJUnitRunner; 11 | 12 | @RunWith(MockitoJUnitRunner.class) 13 | public class ScreenTrackerTest { 14 | 15 | @Mock private TrackingOperations operations; 16 | @Mock private HomeActivity activity; 17 | private ScreenTracker tracker; 18 | 19 | @Before 20 | public void setUp() throws Exception { 21 | tracker = new ScreenTracker(operations); 22 | } 23 | 24 | @Test 25 | public void trackScreenOnResume() { 26 | tracker.onResume(activity); 27 | 28 | verify(operations).trackScreen(activity); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/real-world/src/test/java/com/soundcloud/lightcycle/sample/real_world/utils/DateProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.sample.real_world.utils; 2 | 3 | import static org.hamcrest.core.Is.is; 4 | import static org.junit.Assert.assertThat; 5 | import static org.mockito.Mockito.when; 6 | 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.Mock; 11 | import org.mockito.runners.MockitoJUnitRunner; 12 | 13 | import java.util.Calendar; 14 | 15 | @RunWith(MockitoJUnitRunner.class) 16 | public class DateProviderTest { 17 | private DateProvider dateProvider; 18 | @Mock private Calendar calendar; 19 | 20 | @Before 21 | public void setUp() throws Exception { 22 | dateProvider = new DateProvider(calendar); 23 | } 24 | 25 | @Test 26 | public void midnightIsMorning() { 27 | when(calendar.get(Calendar.HOUR_OF_DAY)).thenReturn(0); 28 | 29 | assertThat(dateProvider.isMorning(), is(true)); 30 | } 31 | 32 | @Test 33 | public void lunchTimeIsNotAnyMoreMorning() { 34 | when(calendar.get(Calendar.HOUR_OF_DAY)).thenReturn(12); 35 | 36 | assertThat(dateProvider.isMorning(), is(false)); 37 | } 38 | 39 | @Test 40 | public void dinerTimeIsNotMorning() { 41 | when(calendar.get(Calendar.HOUR_OF_DAY)).thenReturn(20); 42 | 43 | assertThat(dateProvider.isMorning(), is(false)); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=1.8.0 2 | VERSION_CODE=0 3 | GROUP=com.soundcloud.lightcycle 4 | 5 | android.useAndroidX=true 6 | android.enableJetifier=false 7 | 8 | android.enableUnitTestBinaryResources=true 9 | 10 | POM_DESCRIPTION=LightCycle lets self-contained classes respond to Android’s lifecycle events 11 | POM_URL=https://github.com/soundcloud/lightcycle 12 | POM_SCM_URL=https://github.com/soundcloud/lightcycle 13 | POM_SCM_CONNECTION=scm:git@github.com:soundcloud/lightcycle.git 14 | POM_SCM_DEV_CONNECTION=scm:git@github.com:soundcloud/lightcycle.git 15 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 16 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 17 | POM_LICENCE_DIST=repo 18 | POM_DEVELOPER_ID=SoundCloud 19 | POM_DEVELOPER_NAME=SoundCloud 20 | -------------------------------------------------------------------------------- /gradle/gradle-mvn-push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Chris Banes 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 | 17 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | version = VERSION_NAME 21 | group = GROUP 22 | 23 | def isReleaseBuild() { 24 | return VERSION_NAME.contains("SNAPSHOT") == false 25 | } 26 | 27 | def getReleaseRepositoryUrl() { 28 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 29 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 30 | } 31 | 32 | def getSnapshotRepositoryUrl() { 33 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 34 | : "https://oss.sonatype.org/content/repositories/snapshots/" 35 | } 36 | 37 | def getRepositoryUsername() { 38 | return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : "" 39 | } 40 | 41 | def getRepositoryPassword() { 42 | return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : "" 43 | } 44 | 45 | afterEvaluate { project -> 46 | uploadArchives { 47 | repositories { 48 | mavenDeployer { 49 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 50 | 51 | pom.groupId = GROUP 52 | pom.artifactId = POM_ARTIFACT_ID 53 | pom.version = VERSION_NAME 54 | 55 | repository(url: getReleaseRepositoryUrl()) { 56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 57 | } 58 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 59 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 60 | } 61 | 62 | pom.project { 63 | name POM_NAME 64 | packaging POM_PACKAGING 65 | description POM_DESCRIPTION 66 | url POM_URL 67 | 68 | scm { 69 | url POM_SCM_URL 70 | connection POM_SCM_CONNECTION 71 | developerConnection POM_SCM_DEV_CONNECTION 72 | } 73 | 74 | licenses { 75 | license { 76 | name POM_LICENCE_NAME 77 | url POM_LICENCE_URL 78 | distribution POM_LICENCE_DIST 79 | } 80 | } 81 | 82 | developers { 83 | developer { 84 | id POM_DEVELOPER_ID 85 | name POM_DEVELOPER_NAME 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | signing { 94 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 95 | sign configurations.archives 96 | } 97 | 98 | if (project.getPlugins().hasPlugin('com.android.application') || 99 | project.getPlugins().hasPlugin('com.android.library')) { 100 | task install(type: Upload, dependsOn: assemble) { 101 | repositories.mavenInstaller { 102 | configuration = configurations.archives 103 | 104 | pom.groupId = GROUP 105 | pom.artifactId = POM_ARTIFACT_ID 106 | pom.version = VERSION_NAME 107 | 108 | pom.project { 109 | name POM_NAME 110 | packaging POM_PACKAGING 111 | description POM_DESCRIPTION 112 | url POM_URL 113 | 114 | scm { 115 | url POM_SCM_URL 116 | connection POM_SCM_CONNECTION 117 | developerConnection POM_SCM_DEV_CONNECTION 118 | } 119 | 120 | licenses { 121 | license { 122 | name POM_LICENCE_NAME 123 | url POM_LICENCE_URL 124 | distribution POM_LICENCE_DIST 125 | } 126 | } 127 | 128 | developers { 129 | developer { 130 | id POM_DEVELOPER_ID 131 | name POM_DEVELOPER_NAME 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | task androidJavadocs(type: Javadoc) { 139 | source = android.sourceSets.main.java.source 140 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 141 | } 142 | 143 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 144 | classifier = 'javadoc' 145 | from androidJavadocs.destinationDir 146 | } 147 | 148 | task androidSourcesJar(type: Jar) { 149 | classifier = 'sources' 150 | from android.sourceSets.main.java.source 151 | } 152 | } else { 153 | install { 154 | repositories.mavenInstaller { 155 | pom.groupId = GROUP 156 | pom.artifactId = POM_ARTIFACT_ID 157 | pom.version = VERSION_NAME 158 | 159 | pom.project { 160 | name POM_NAME 161 | packaging POM_PACKAGING 162 | description POM_DESCRIPTION 163 | url POM_URL 164 | 165 | scm { 166 | url POM_SCM_URL 167 | connection POM_SCM_CONNECTION 168 | developerConnection POM_SCM_DEV_CONNECTION 169 | } 170 | 171 | licenses { 172 | license { 173 | name POM_LICENCE_NAME 174 | url POM_LICENCE_URL 175 | distribution POM_LICENCE_DIST 176 | } 177 | } 178 | 179 | developers { 180 | developer { 181 | id POM_DEVELOPER_ID 182 | name POM_DEVELOPER_NAME 183 | } 184 | } 185 | } 186 | } 187 | } 188 | 189 | task sourcesJar(type: Jar, dependsOn:classes) { 190 | classifier = 'sources' 191 | from sourceSets.main.allSource 192 | } 193 | 194 | task javadocJar(type: Jar, dependsOn:javadoc) { 195 | classifier = 'javadoc' 196 | from javadoc.destinationDir 197 | } 198 | } 199 | 200 | if (JavaVersion.current().isJava8Compatible()) { 201 | allprojects { 202 | tasks.withType(Javadoc) { 203 | options.addStringOption('Xdoclint:none', '-quiet') 204 | } 205 | } 206 | } 207 | 208 | artifacts { 209 | if (project.getPlugins().hasPlugin('com.android.application') || 210 | project.getPlugins().hasPlugin('com.android.library')) { 211 | archives androidSourcesJar 212 | archives androidJavadocsJar 213 | } else { 214 | archives sourcesJar 215 | archives javadocJar 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soundcloud/lightcycle/35207504e3785d7c162189f51d475ba75fbd81cb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /lightcycle-api/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | apply plugin: 'checkstyle' 3 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 4 | 5 | checkstyle { 6 | configFile rootProject.file('checkstyle.xml') 7 | showViolations true 8 | } 9 | 10 | dependencies { 11 | compileOnly deps.android 12 | 13 | testImplementation deps.android 14 | testImplementation deps.junit 15 | testImplementation deps.equalsverifier 16 | } 17 | -------------------------------------------------------------------------------- /lightcycle-api/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=LightCycle API 2 | POM_ARTIFACT_ID=lightcycle-api 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /lightcycle-api/src/main/java/com/soundcloud/lightcycle/ActivityLightCycle.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.MenuItem; 6 | 7 | public interface ActivityLightCycle { 8 | void onCreate(T host, Bundle bundle); 9 | void onNewIntent(T host, Intent intent); 10 | void onStart(T host); 11 | void onResume(T host); 12 | boolean onOptionsItemSelected(T host, MenuItem item); 13 | void onPause(T host); 14 | void onStop(T host); 15 | void onSaveInstanceState(T host, Bundle bundle); 16 | void onRestoreInstanceState(T host, Bundle bundle); 17 | void onDestroy(T host); 18 | } 19 | -------------------------------------------------------------------------------- /lightcycle-api/src/main/java/com/soundcloud/lightcycle/DefaultActivityLightCycle.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.MenuItem; 6 | 7 | public class DefaultActivityLightCycle implements ActivityLightCycle { 8 | @Override 9 | public void onCreate(HostType host, Bundle bundle) { /* no-op */ } 10 | 11 | @Override 12 | public void onNewIntent(HostType host, Intent intent) { /* no-op */ } 13 | 14 | @Override 15 | public void onStart(HostType host) { /* no-op */ } 16 | 17 | @Override 18 | public void onResume(HostType host) { /* no-op */ } 19 | 20 | @Override 21 | public boolean onOptionsItemSelected(HostType host, MenuItem item) { 22 | return false; 23 | } 24 | 25 | @Override 26 | public void onPause(HostType host) { /* no-op */ } 27 | 28 | @Override 29 | public void onStop(HostType host) { /* no-op */ } 30 | 31 | @Override 32 | public void onSaveInstanceState(HostType host, Bundle bundle) { /* no-op */ } 33 | 34 | @Override 35 | public void onRestoreInstanceState(HostType host, Bundle bundle) { /* no-op */ } 36 | 37 | @Override 38 | public void onDestroy(HostType host) { /* no-op */ } 39 | } 40 | -------------------------------------------------------------------------------- /lightcycle-api/src/main/java/com/soundcloud/lightcycle/DefaultFragmentLightCycle.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.view.MenuItem; 9 | import android.view.View; 10 | 11 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 12 | public class DefaultFragmentLightCycle implements FragmentLightCycle { 13 | @Override 14 | public void onAttach(HostType host, Activity activity) { /* no-op */ } 15 | 16 | @Override 17 | public void onAttach(HostType host, Context context) { /* no-op */ } 18 | 19 | @Override 20 | public void onCreate(HostType host, Bundle bundle) { /* no-op */ } 21 | 22 | @Override 23 | public void onViewCreated(HostType host, View view, Bundle savedInstanceState) { /* no-op */ } 24 | 25 | @Override 26 | public void onActivityCreated(HostType host, Bundle bundle) { /* no-op */ } 27 | 28 | @Override 29 | public void onStart(HostType host) { /* no-op */ } 30 | 31 | @Override 32 | public void onResume(HostType host) { /* no-op */ } 33 | 34 | @Override 35 | public boolean onOptionsItemSelected(HostType host, MenuItem item) { 36 | return false; 37 | } 38 | 39 | @Override 40 | public void onPause(HostType host) { /* no-op */ } 41 | 42 | @Override 43 | public void onStop(HostType host) { /* no-op */ } 44 | 45 | @Override 46 | public void onSaveInstanceState(HostType host, Bundle bundle) { /* no-op */ } 47 | 48 | @Override 49 | public void onDestroyView(HostType host) { /* no-op */ } 50 | 51 | @Override 52 | public void onDestroy(HostType host) { /* no-op */ } 53 | 54 | @Override 55 | public void onDetach(HostType host) { /* no-op */ } 56 | } 57 | -------------------------------------------------------------------------------- /lightcycle-api/src/main/java/com/soundcloud/lightcycle/DefaultSupportFragmentLightCycle.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.MenuItem; 6 | import android.view.View; 7 | 8 | public class DefaultSupportFragmentLightCycle implements SupportFragmentLightCycle { 9 | @Override 10 | public void onAttach(HostType host, Activity activity) { /* no-op */ } 11 | 12 | @Override 13 | public void onCreate(HostType host, Bundle bundle) { /* no-op */ } 14 | 15 | @Override 16 | public void onViewCreated(HostType host, View view, Bundle savedInstanceState) { /* no-op */ } 17 | 18 | @Override 19 | public void onActivityCreated(HostType host, Bundle bundle) { /* no-op */ } 20 | 21 | @Override 22 | public void onStart(HostType host) { /* no-op */ } 23 | 24 | @Override 25 | public void onResume(HostType host) { /* no-op */ } 26 | 27 | @Override 28 | public boolean onOptionsItemSelected(HostType host, MenuItem item) { 29 | return false; 30 | } 31 | 32 | @Override 33 | public void onPause(HostType host) { /* no-op */ } 34 | 35 | @Override 36 | public void onStop(HostType host) { /* no-op */ } 37 | 38 | @Override 39 | public void onSaveInstanceState(HostType host, Bundle bundle) { /* no-op */ } 40 | 41 | @Override 42 | public void onDestroyView(HostType host) { /* no-op */ } 43 | 44 | @Override 45 | public void onDestroy(HostType host) { /* no-op */ } 46 | 47 | @Override 48 | public void onDetach(HostType host) { /* no-op */ } 49 | } 50 | -------------------------------------------------------------------------------- /lightcycle-api/src/main/java/com/soundcloud/lightcycle/FragmentLightCycle.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.view.MenuItem; 7 | import android.view.View; 8 | 9 | public interface FragmentLightCycle { 10 | void onAttach(T host, Activity activity); 11 | void onAttach(T host, Context context); 12 | void onCreate(T host, Bundle bundle); 13 | void onViewCreated(T host, View view, Bundle savedInstanceState); 14 | void onActivityCreated(T host, Bundle bundle); 15 | void onStart(T host); 16 | void onResume(T host); 17 | boolean onOptionsItemSelected(T host, MenuItem item); 18 | void onPause(T host); 19 | void onStop(T host); 20 | void onSaveInstanceState(T host, Bundle bundle); 21 | void onDestroyView(T host); 22 | void onDestroy(T host); 23 | void onDetach(T host); 24 | } 25 | -------------------------------------------------------------------------------- /lightcycle-api/src/main/java/com/soundcloud/lightcycle/LightCycle.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Retention(RetentionPolicy.CLASS) 10 | @Target(ElementType.FIELD) 11 | public @interface LightCycle { 12 | } 13 | -------------------------------------------------------------------------------- /lightcycle-api/src/main/java/com/soundcloud/lightcycle/LightCycleDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | // TODO : rename ? 4 | public interface LightCycleDispatcher { 5 | 6 | void bind(LightCycle lightCycle); 7 | 8 | } 9 | -------------------------------------------------------------------------------- /lightcycle-api/src/main/java/com/soundcloud/lightcycle/SupportFragmentLightCycle.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.MenuItem; 6 | import android.view.View; 7 | 8 | public interface SupportFragmentLightCycle { 9 | void onAttach(HostType host, Activity activity); 10 | void onCreate(HostType host, Bundle bundle); 11 | void onViewCreated(HostType host, View view, Bundle savedInstanceState); 12 | void onActivityCreated(HostType host, Bundle bundle); 13 | void onStart(HostType host); 14 | void onResume(HostType host); 15 | boolean onOptionsItemSelected(HostType host, MenuItem item); 16 | void onPause(HostType host); 17 | void onStop(HostType host); 18 | void onSaveInstanceState(HostType host, Bundle bundle); 19 | void onDestroyView(HostType host); 20 | void onDestroy(HostType host); 21 | void onDetach(HostType host); 22 | } 23 | -------------------------------------------------------------------------------- /lightcycle-api/src/test/java/com/soundcloud/lightcycle/LightCyclesTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.core.IsEqual.equalTo; 5 | import static org.hamcrest.core.IsNot.not; 6 | import static org.hamcrest.core.IsSame.sameInstance; 7 | 8 | import nl.jqno.equalsverifier.EqualsVerifier; 9 | 10 | import org.junit.Test; 11 | 12 | import android.app.Activity; 13 | import android.app.Fragment; 14 | 15 | public class LightCyclesTest { 16 | 17 | @Test 18 | public void liftedActivityLightCycleEqualsContract() { 19 | EqualsVerifier.forClass(LightCycles.LiftedActivityLightCycle.class).verify(); 20 | } 21 | 22 | @Test 23 | public void liftedFragmentLightCycleEqualsContract() { 24 | EqualsVerifier.forClass(LightCycles.LiftedFragmentLightCycle.class).verify(); 25 | } 26 | 27 | @Test 28 | public void liftedSupportFragmentLightCycleEqualsContract() { 29 | EqualsVerifier.forClass(LightCycles.LiftedSupportFragmentLightCycle.class).verify(); 30 | } 31 | 32 | @Test 33 | public void liftedActivityLightCycleAreEqualsWhenTheSourceIsTheSame() { 34 | final DefaultActivityLightCycle lightCycle = new DefaultActivityLightCycle<>(); 35 | final ActivityLightCycle lift1 = LightCycles.lift(lightCycle); 36 | final ActivityLightCycle lift2 = LightCycles.lift(lightCycle); 37 | 38 | assertThat(lift1, not(sameInstance(lift2))); 39 | assertThat(lift1, equalTo(lift2)); 40 | } 41 | 42 | @Test 43 | public void liftedFragmentLightCycleAreEqualsWhenTheSourceIsTheSame() { 44 | final DefaultFragmentLightCycle lightCycle = new DefaultFragmentLightCycle<>(); 45 | final FragmentLightCycle lift1 = LightCycles.lift(lightCycle); 46 | final FragmentLightCycle lift2 = LightCycles.lift(lightCycle); 47 | 48 | assertThat(lift1, not(sameInstance(lift2))); 49 | assertThat(lift1, equalTo(lift2)); 50 | } 51 | 52 | @Test 53 | public void liftedSupportFragmentLightCycleAreEqualsWhenTheSourceIsTheSame() { 54 | final DefaultSupportFragmentLightCycle lightCycle = new DefaultSupportFragmentLightCycle<>(); 55 | final SupportFragmentLightCycle lift1 = LightCycles.lift(lightCycle); 56 | final SupportFragmentLightCycle lift2 = LightCycles.lift(lightCycle); 57 | 58 | assertThat(lift1, not(sameInstance(lift2))); 59 | assertThat(lift1, equalTo(lift2)); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /lightcycle-integration-test/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /lightcycle-integration-test/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 | minSdkVersion rootProject.ext.minSdkVersion 13 | targetSdkVersion rootProject.ext.targetSdkVersion 14 | versionCode 1 15 | versionName "1.0" 16 | } 17 | 18 | testOptions { 19 | unitTests { 20 | includeAndroidResources = true 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | annotationProcessor project(":lightcycle-processor") 27 | 28 | implementation project(":lightcycle-lib") 29 | 30 | implementation deps.appcompat 31 | implementation deps.fragment_testing 32 | 33 | testImplementation deps.roboelectric 34 | testImplementation deps.compiletesting 35 | testImplementation deps.fragment_testing 36 | testImplementation deps.junit 37 | testImplementation deps.androidx_junit 38 | } 39 | -------------------------------------------------------------------------------- /lightcycle-integration-test/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/yuwono-niko/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 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/java/com/soundcloud/lightcycle/integration_test/ActivityLogger.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.MenuItem; 6 | 7 | import com.soundcloud.lightcycle.integration_test.callback.ActivityLifecycleCallback; 8 | 9 | public class ActivityLogger extends BaseActivityLogger { 10 | 11 | @Override 12 | public void onCreate(SampleActivity activity, Bundle bundle) { 13 | super.onCreate(activity, bundle); 14 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onCreate, true); 15 | } 16 | 17 | @Override 18 | public void onStart(SampleActivity activity) { 19 | super.onStart(activity); 20 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onStart, true); 21 | } 22 | 23 | @Override 24 | public void onRestoreInstanceState(SampleActivity activity, Bundle bundle) { 25 | super.onRestoreInstanceState(activity, bundle); 26 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onRestoreInstanceState, true); 27 | } 28 | 29 | @Override 30 | public void onResume(SampleActivity activity) { 31 | super.onResume(activity); 32 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onResume, true); 33 | } 34 | 35 | @Override 36 | public void onPause(SampleActivity activity) { 37 | super.onPause(activity); 38 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onPause, true); 39 | } 40 | 41 | @Override 42 | public void onSaveInstanceState(SampleActivity activity, Bundle bundle) { 43 | super.onSaveInstanceState(activity, bundle); 44 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onSaveInstanceState, true); 45 | } 46 | 47 | @Override 48 | public void onStop(SampleActivity activity) { 49 | super.onStop(activity); 50 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onStop, true); 51 | } 52 | 53 | @Override 54 | public void onDestroy(SampleActivity activity) { 55 | super.onDestroy(activity); 56 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onDestroy, true); 57 | } 58 | 59 | @Override 60 | public void onNewIntent(SampleActivity activity, Intent intent) { 61 | super.onNewIntent(activity, intent); 62 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onNewIntent, true); 63 | } 64 | 65 | @Override 66 | public boolean onOptionsItemSelected(SampleActivity activity, MenuItem item) { 67 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onOptionsItemSelected, true); 68 | return super.onOptionsItemSelected(activity, item); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/java/com/soundcloud/lightcycle/integration_test/AppCompatActivityLogger.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.MenuItem; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | import com.soundcloud.lightcycle.integration_test.callback.ActivityLifecycleCallback; 10 | 11 | class AppCompatActivityLogger extends BaseActivityLogger { 12 | 13 | @Override 14 | public void onCreate(AppCompatActivity activity, Bundle bundle) { 15 | super.onCreate(activity, bundle); 16 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onCreate, true); 17 | } 18 | 19 | @Override 20 | public void onStart(AppCompatActivity activity) { 21 | super.onStart(activity); 22 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onStart, true); 23 | } 24 | 25 | @Override 26 | public void onRestoreInstanceState(AppCompatActivity activity, Bundle bundle) { 27 | super.onRestoreInstanceState(activity, bundle); 28 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onRestoreInstanceState, true); 29 | } 30 | 31 | @Override 32 | public void onResume(AppCompatActivity activity) { 33 | super.onResume(activity); 34 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onResume, true); 35 | } 36 | 37 | @Override 38 | public void onPause(AppCompatActivity activity) { 39 | super.onPause(activity); 40 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onPause, true); 41 | } 42 | 43 | @Override 44 | public void onSaveInstanceState(AppCompatActivity activity, Bundle bundle) { 45 | super.onSaveInstanceState(activity, bundle); 46 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onSaveInstanceState, true); 47 | } 48 | 49 | @Override 50 | public void onStop(AppCompatActivity activity) { 51 | super.onStop(activity); 52 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onStop, true); 53 | } 54 | 55 | @Override 56 | public void onDestroy(AppCompatActivity activity) { 57 | super.onDestroy(activity); 58 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onDestroy, true); 59 | } 60 | 61 | @Override 62 | public void onNewIntent(AppCompatActivity activity, Intent intent) { 63 | super.onNewIntent(activity, intent); 64 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onNewIntent, true); 65 | } 66 | 67 | @Override 68 | public boolean onOptionsItemSelected(AppCompatActivity activity, MenuItem item) { 69 | lifecycleCallbackCallState.put(ActivityLifecycleCallback.onOptionsItemSelected, true); 70 | return super.onOptionsItemSelected(activity, item); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/java/com/soundcloud/lightcycle/integration_test/BaseActivityLogger.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import android.app.Activity; 4 | 5 | import com.soundcloud.lightcycle.DefaultActivityLightCycle; 6 | import com.soundcloud.lightcycle.integration_test.callback.ActivityLifecycleCallback; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | class BaseActivityLogger extends DefaultActivityLightCycle { 12 | 13 | Map lifecycleCallbackCallState; 14 | 15 | BaseActivityLogger() { 16 | initializeLifecycleCallbackCallStateMap(); 17 | } 18 | 19 | private void initializeLifecycleCallbackCallStateMap() { 20 | this.lifecycleCallbackCallState = new HashMap<>(); 21 | this.lifecycleCallbackCallState.put(ActivityLifecycleCallback.onCreate, false); 22 | this.lifecycleCallbackCallState.put(ActivityLifecycleCallback.onStart, false); 23 | this.lifecycleCallbackCallState.put(ActivityLifecycleCallback.onRestoreInstanceState, false); 24 | this.lifecycleCallbackCallState.put(ActivityLifecycleCallback.onResume, false); 25 | this.lifecycleCallbackCallState.put(ActivityLifecycleCallback.onPause, false); 26 | this.lifecycleCallbackCallState.put(ActivityLifecycleCallback.onSaveInstanceState, false); 27 | this.lifecycleCallbackCallState.put(ActivityLifecycleCallback.onStop, false); 28 | this.lifecycleCallbackCallState.put(ActivityLifecycleCallback.onDestroy, false); 29 | this.lifecycleCallbackCallState.put(ActivityLifecycleCallback.onNewIntent, false); 30 | this.lifecycleCallbackCallState.put(ActivityLifecycleCallback.onOptionsItemSelected, false); 31 | } 32 | 33 | boolean isActivityLifecycleCallbackCalled(ActivityLifecycleCallback callback) { 34 | return lifecycleCallbackCallState.get(callback); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/java/com/soundcloud/lightcycle/integration_test/FragmentLogger.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | 8 | import com.soundcloud.lightcycle.DefaultFragmentLightCycle; 9 | import com.soundcloud.lightcycle.integration_test.callback.FragmentLifecycleCallback; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class FragmentLogger extends DefaultFragmentLightCycle { 15 | 16 | private Map lifecycleCallbackCallState; 17 | 18 | FragmentLogger() { 19 | initializeLifecycleCallbackCallStateMap(); 20 | } 21 | 22 | @Override 23 | public void onAttach(SampleFragment fragment, Activity activity) { 24 | super.onAttach(fragment, activity); 25 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onAttach, true); 26 | } 27 | 28 | @Override 29 | public void onAttach(SampleFragment fragment, Context context) { 30 | super.onAttach(fragment, context); 31 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onAttach, true); 32 | } 33 | 34 | @Override 35 | public void onCreate(SampleFragment fragment, Bundle bundle) { 36 | super.onCreate(fragment, bundle); 37 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onCreate, true); 38 | } 39 | 40 | @Override 41 | public void onViewCreated(SampleFragment fragment, View view, Bundle savedInstanceState) { 42 | super.onViewCreated(fragment, view, savedInstanceState); 43 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onViewCreated, true); 44 | } 45 | 46 | @Override 47 | public void onActivityCreated(SampleFragment fragment, Bundle bundle) { 48 | super.onActivityCreated(fragment, bundle); 49 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onActivityCreated, true); 50 | } 51 | 52 | @Override 53 | public void onStart(SampleFragment fragment) { 54 | super.onStart(fragment); 55 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onStart, true); 56 | } 57 | 58 | @Override 59 | public void onResume(SampleFragment fragment) { 60 | super.onResume(fragment); 61 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onResume, true); 62 | } 63 | 64 | @Override 65 | public void onPause(SampleFragment fragment) { 66 | super.onPause(fragment); 67 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onPause, true); 68 | } 69 | 70 | @Override 71 | public void onStop(SampleFragment fragment) { 72 | super.onStop(fragment); 73 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onStop, true); 74 | } 75 | 76 | @Override 77 | public void onSaveInstanceState(SampleFragment fragment, Bundle bundle) { 78 | super.onSaveInstanceState(fragment, bundle); 79 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onSaveInstanceState, true); 80 | } 81 | 82 | @Override 83 | public void onDestroyView(SampleFragment fragment) { 84 | super.onDestroyView(fragment); 85 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onDestroyView, true); 86 | } 87 | 88 | @Override 89 | public void onDestroy(SampleFragment fragment) { 90 | super.onDestroy(fragment); 91 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onDestroy, true); 92 | } 93 | 94 | @Override 95 | public void onDetach(SampleFragment fragment) { 96 | super.onDetach(fragment); 97 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onDetach, true); 98 | } 99 | 100 | boolean isFragmentLifecycleCallbackCalled(FragmentLifecycleCallback callback) { 101 | return lifecycleCallbackCallState.get(callback); 102 | } 103 | 104 | private void initializeLifecycleCallbackCallStateMap() { 105 | this.lifecycleCallbackCallState = new HashMap<>(); 106 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onAttach, false); 107 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onCreate, false); 108 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onViewCreated, false); 109 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onActivityCreated, false); 110 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onStart, false); 111 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onResume, false); 112 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onPause, false); 113 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onStop, false); 114 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onSaveInstanceState, false); 115 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onDestroyView, false); 116 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onDestroy, false); 117 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onDetach, false); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/java/com/soundcloud/lightcycle/integration_test/SampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import android.view.Menu; 4 | 5 | import com.soundcloud.lightcycle.ActivityLightCycle; 6 | import com.soundcloud.lightcycle.LightCycle; 7 | import com.soundcloud.lightcycle.LightCycleActivity; 8 | import com.soundcloud.lightcycle.LightCycleDispatcher; 9 | import com.soundcloud.lightcycle.sample.basic.R; 10 | 11 | public class SampleActivity 12 | extends LightCycleActivity 13 | implements LightCycleDispatcher> { 14 | 15 | @LightCycle 16 | ActivityLogger activityLogger = new ActivityLogger(); 17 | 18 | @Override 19 | protected void setActivityContentView() { 20 | setContentView(R.layout.activity_sample); 21 | } 22 | 23 | @Override 24 | public boolean onCreateOptionsMenu(Menu menu) { 25 | getMenuInflater().inflate(R.menu.menu_search, menu); 26 | return super.onCreateOptionsMenu(menu); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/java/com/soundcloud/lightcycle/integration_test/SampleAppCompatActivity.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import android.view.Menu; 4 | 5 | import com.soundcloud.lightcycle.LightCycle; 6 | import com.soundcloud.lightcycle.LightCycleAppCompatActivity; 7 | import com.soundcloud.lightcycle.sample.basic.R; 8 | 9 | public class SampleAppCompatActivity extends LightCycleAppCompatActivity { 10 | @LightCycle 11 | AppCompatActivityLogger appCompatActivityLogger = new AppCompatActivityLogger(); 12 | 13 | @Override 14 | protected void setActivityContentView() { 15 | setContentView(R.layout.activity_sample); 16 | } 17 | 18 | @Override 19 | public boolean onCreateOptionsMenu(Menu menu) { 20 | getMenuInflater().inflate(R.menu.menu_search, menu); 21 | return super.onCreateOptionsMenu(menu); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/java/com/soundcloud/lightcycle/integration_test/SampleFragment.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import com.soundcloud.lightcycle.FragmentLightCycle; 4 | import com.soundcloud.lightcycle.LightCycle; 5 | import com.soundcloud.lightcycle.LightCycleFragment; 6 | import com.soundcloud.lightcycle.sample.basic.R; 7 | 8 | import android.annotation.TargetApi; 9 | import android.app.Activity; 10 | import android.os.Build; 11 | import android.os.Bundle; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | 16 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 17 | public class SampleFragment extends LightCycleFragment { 18 | 19 | @LightCycle FragmentLogger fragmentLogger = new FragmentLogger(); 20 | int bindCount = 0; 21 | int onAttachCount = 0; 22 | 23 | @Override 24 | public void bind(FragmentLightCycle lifeCycleComponent) { 25 | super.bind(lifeCycleComponent); 26 | bindCount++; 27 | } 28 | 29 | @Override 30 | public void onAttach(Activity activity) { 31 | super.onAttach(activity); 32 | onAttachCount++; 33 | } 34 | 35 | @Override 36 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 37 | return inflater.inflate(R.layout.fragment_sample, container, false); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/java/com/soundcloud/lightcycle/integration_test/SampleFragmentDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | 4 | import android.app.Fragment; 5 | 6 | import com.soundcloud.lightcycle.DefaultFragmentLightCycle; 7 | import com.soundcloud.lightcycle.FragmentLightCycle; 8 | import com.soundcloud.lightcycle.FragmentLightCycleDispatcher; 9 | import com.soundcloud.lightcycle.LightCycle; 10 | 11 | class SampleFragmentDispatcher extends FragmentLightCycleDispatcher { 12 | @LightCycle 13 | DefaultFragmentLightCycle lightCycle = new DefaultFragmentLightCycle<>(); 14 | 15 | int bindCount = 0; 16 | 17 | @Override 18 | public void bind(FragmentLightCycle lightCycle) { 19 | super.bind(lightCycle); 20 | bindCount++; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/java/com/soundcloud/lightcycle/integration_test/SampleSupportFragment.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import com.soundcloud.lightcycle.LightCycle; 4 | import com.soundcloud.lightcycle.LightCycleSupportFragment; 5 | import com.soundcloud.lightcycle.SupportFragmentLightCycle; 6 | import com.soundcloud.lightcycle.sample.basic.R; 7 | 8 | import android.app.Activity; 9 | import android.os.Bundle; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | 14 | public class SampleSupportFragment extends LightCycleSupportFragment { 15 | 16 | @LightCycle 17 | SupportFragmentLogger supportFragmentLogger = new SupportFragmentLogger(); 18 | public int onAttachCount; 19 | public int bindCount; 20 | 21 | @Override 22 | public void onAttach(Activity activity) { 23 | super.onAttach(activity); 24 | onAttachCount++; 25 | } 26 | 27 | @Override 28 | public void bind(SupportFragmentLightCycle lifeCycleComponent) { 29 | super.bind(lifeCycleComponent); 30 | bindCount++; 31 | } 32 | 33 | @Override 34 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 35 | return inflater.inflate(R.layout.fragment_sample, container, false); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/java/com/soundcloud/lightcycle/integration_test/SampleSupportFragmentDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import androidx.fragment.app.Fragment; 4 | 5 | import com.soundcloud.lightcycle.DefaultSupportFragmentLightCycle; 6 | import com.soundcloud.lightcycle.LightCycle; 7 | import com.soundcloud.lightcycle.SupportFragmentLightCycle; 8 | import com.soundcloud.lightcycle.SupportFragmentLightCycleDispatcher; 9 | 10 | class SampleSupportFragmentDispatcher extends SupportFragmentLightCycleDispatcher { 11 | @LightCycle DefaultSupportFragmentLightCycle lightCycle = new DefaultSupportFragmentLightCycle<>(); 12 | 13 | int bindCount = 0; 14 | 15 | @Override 16 | public void bind(SupportFragmentLightCycle lightCycle) { 17 | super.bind(lightCycle); 18 | bindCount++; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/java/com/soundcloud/lightcycle/integration_test/SupportFragmentLogger.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | 7 | import com.soundcloud.lightcycle.DefaultSupportFragmentLightCycle; 8 | import com.soundcloud.lightcycle.integration_test.callback.FragmentLifecycleCallback; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public class SupportFragmentLogger extends DefaultSupportFragmentLightCycle { 14 | 15 | private Map lifecycleCallbackCallState; 16 | 17 | SupportFragmentLogger() { 18 | initializeLifecycleCallbackCallStateMap(); 19 | } 20 | 21 | @Override 22 | public void onAttach(SampleSupportFragment fragment, Activity activity) { 23 | super.onAttach(fragment, activity); 24 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onAttach, true); 25 | } 26 | 27 | @Override 28 | public void onCreate(SampleSupportFragment fragment, Bundle bundle) { 29 | super.onCreate(fragment, bundle); 30 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onCreate, true); 31 | } 32 | 33 | @Override 34 | public void onViewCreated(SampleSupportFragment fragment, View view, Bundle savedInstanceState) { 35 | super.onViewCreated(fragment, view, savedInstanceState); 36 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onViewCreated, true); 37 | } 38 | 39 | @Override 40 | public void onActivityCreated(SampleSupportFragment fragment, Bundle bundle) { 41 | super.onActivityCreated(fragment, bundle); 42 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onActivityCreated, true); 43 | } 44 | 45 | @Override 46 | public void onStart(SampleSupportFragment fragment) { 47 | super.onStart(fragment); 48 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onStart, true); 49 | } 50 | 51 | @Override 52 | public void onResume(SampleSupportFragment fragment) { 53 | super.onResume(fragment); 54 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onResume, true); 55 | } 56 | 57 | @Override 58 | public void onPause(SampleSupportFragment fragment) { 59 | super.onPause(fragment); 60 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onPause, true); 61 | } 62 | 63 | @Override 64 | public void onStop(SampleSupportFragment fragment) { 65 | super.onStop(fragment); 66 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onStop, true); 67 | } 68 | 69 | @Override 70 | public void onDestroyView(SampleSupportFragment fragment) { 71 | super.onDestroyView(fragment); 72 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onDestroyView, true); 73 | } 74 | 75 | @Override 76 | public void onDestroy(SampleSupportFragment fragment) { 77 | super.onDestroy(fragment); 78 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onDestroy, true); 79 | } 80 | 81 | @Override 82 | public void onDetach(SampleSupportFragment fragment) { 83 | super.onDetach(fragment); 84 | lifecycleCallbackCallState.put(FragmentLifecycleCallback.onDetach, true); 85 | } 86 | 87 | boolean isFragmentLifecycleCallbackCalled(FragmentLifecycleCallback callback) { 88 | return lifecycleCallbackCallState.get(callback); 89 | } 90 | 91 | private void initializeLifecycleCallbackCallStateMap() { 92 | this.lifecycleCallbackCallState = new HashMap<>(); 93 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onAttach, false); 94 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onCreate, false); 95 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onViewCreated, false); 96 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onActivityCreated, false); 97 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onStart, false); 98 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onResume, false); 99 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onPause, false); 100 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onStop, false); 101 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onSaveInstanceState, false); 102 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onDestroyView, false); 103 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onDestroy, false); 104 | this.lifecycleCallbackCallState.put(FragmentLifecycleCallback.onDetach, false); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/java/com/soundcloud/lightcycle/integration_test/callback/ActivityLifecycleCallback.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test.callback; 2 | 3 | public enum ActivityLifecycleCallback { 4 | onCreate, 5 | onStart, 6 | onResume, 7 | onPause, 8 | onStop, 9 | onDestroy, 10 | onSaveInstanceState, 11 | onRestoreInstanceState, 12 | onNewIntent, 13 | onOptionsItemSelected 14 | } -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/java/com/soundcloud/lightcycle/integration_test/callback/FragmentLifecycleCallback.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test.callback; 2 | 3 | public enum FragmentLifecycleCallback { 4 | onAttach, 5 | onCreate, 6 | onViewCreated, 7 | onActivityCreated, 8 | onStart, 9 | onResume, 10 | onPause, 11 | onStop, 12 | onSaveInstanceState, 13 | onDestroyView, 14 | onDestroy, 15 | onDetach 16 | } 17 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/res/layout/activity_sample.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/res/layout/fragment_sample.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/res/menu/menu_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff 3 | #bbbbbb 4 | @color/soundcloud 5 | #ff5500 6 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | lightcycle-integration-test] 3 | search 4 | 5 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/test/java/com/soundcloud/lightcycle/integration_test/ActivityLoggerTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import com.google.common.truth.BooleanSubject; 9 | import com.soundcloud.lightcycle.integration_test.callback.ActivityLifecycleCallback; 10 | import com.soundcloud.lightcycle.sample.basic.BuildConfig; 11 | import com.soundcloud.lightcycle.sample.basic.R; 12 | 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.robolectric.Robolectric; 16 | import org.robolectric.RobolectricTestRunner; 17 | import org.robolectric.Shadows; 18 | import org.robolectric.android.controller.ActivityController; 19 | import org.robolectric.annotation.Config; 20 | import org.robolectric.shadows.ShadowActivity; 21 | 22 | import java.util.Arrays; 23 | import java.util.Collections; 24 | import java.util.List; 25 | 26 | import static com.google.common.truth.Truth.assertThat; 27 | 28 | @RunWith(AndroidJUnit4.class) 29 | public class ActivityLoggerTest { 30 | private final ActivityController controller = Robolectric.buildActivity(SampleActivity.class); 31 | private final SampleActivity sampleActivity = controller.get(); 32 | private final ActivityLogger activityLogger = sampleActivity.activityLogger; 33 | 34 | @Test 35 | public void onCreate() { 36 | controller.create(); 37 | assertLifecycleCallbackCallIsCorrect( 38 | Collections.singletonList(ActivityLifecycleCallback.onCreate) 39 | ); 40 | } 41 | 42 | @Test 43 | public void onStart() { 44 | controller.create() 45 | .start(); 46 | assertLifecycleCallbackCallIsCorrect( 47 | Arrays.asList(ActivityLifecycleCallback.onCreate, 48 | ActivityLifecycleCallback.onStart) 49 | ); 50 | } 51 | 52 | @Test 53 | public void onRestoreInstance() { 54 | controller.create() 55 | .start() 56 | .restoreInstanceState(new Bundle()); 57 | assertLifecycleCallbackCallIsCorrect( 58 | Arrays.asList(ActivityLifecycleCallback.onCreate, 59 | ActivityLifecycleCallback.onStart, 60 | ActivityLifecycleCallback.onRestoreInstanceState) 61 | ); 62 | } 63 | 64 | @Test 65 | public void onResume() { 66 | controller.create() 67 | .start() 68 | .resume(); 69 | assertLifecycleCallbackCallIsCorrect( 70 | Arrays.asList(ActivityLifecycleCallback.onCreate, 71 | ActivityLifecycleCallback.onStart, 72 | ActivityLifecycleCallback.onResume) 73 | ); 74 | } 75 | 76 | @Test 77 | public void onPause() { 78 | controller.create() 79 | .start() 80 | .resume() 81 | .pause(); 82 | assertLifecycleCallbackCallIsCorrect( 83 | Arrays.asList(ActivityLifecycleCallback.onCreate, 84 | ActivityLifecycleCallback.onStart, 85 | ActivityLifecycleCallback.onResume, 86 | ActivityLifecycleCallback.onPause) 87 | ); 88 | } 89 | 90 | @Test 91 | public void onSaveInstanceState() { 92 | controller.create() 93 | .start() 94 | .resume() 95 | .pause() 96 | .saveInstanceState(new Bundle()); 97 | assertLifecycleCallbackCallIsCorrect( 98 | Arrays.asList(ActivityLifecycleCallback.onCreate, 99 | ActivityLifecycleCallback.onStart, 100 | ActivityLifecycleCallback.onResume, 101 | ActivityLifecycleCallback.onPause, 102 | ActivityLifecycleCallback.onSaveInstanceState) 103 | ); 104 | } 105 | 106 | @Test 107 | public void onStop() { 108 | controller.create() 109 | .start() 110 | .stop(); 111 | assertLifecycleCallbackCallIsCorrect( 112 | Arrays.asList(ActivityLifecycleCallback.onCreate, 113 | ActivityLifecycleCallback.onStart, 114 | ActivityLifecycleCallback.onStop) 115 | ); 116 | } 117 | 118 | @Test 119 | public void onDestroy() { 120 | controller.create() 121 | .destroy(); 122 | assertLifecycleCallbackCallIsCorrect( 123 | Arrays.asList(ActivityLifecycleCallback.onCreate, 124 | ActivityLifecycleCallback.onDestroy) 125 | ); 126 | } 127 | 128 | @Test 129 | public void onNewIntent() { 130 | controller.setup() 131 | .newIntent(new Intent()); 132 | assertThat(activityLogger.isActivityLifecycleCallbackCalled(ActivityLifecycleCallback.onNewIntent)).isTrue(); 133 | } 134 | 135 | @Test 136 | public void onOptionsItemSelected() { 137 | controller.setup(); 138 | ShadowActivity shadowActivity = Shadows.shadowOf(controller.get()); 139 | shadowActivity.clickMenuItem(R.id.action_search); 140 | assertThat(activityLogger.isActivityLifecycleCallbackCalled(ActivityLifecycleCallback.onOptionsItemSelected)).isTrue(); 141 | } 142 | 143 | private void assertLifecycleCallbackCallIsCorrect(List callbacks) { 144 | for (ActivityLifecycleCallback activityLifecycleCallback : ActivityLifecycleCallback.values()) { 145 | BooleanSubject subject = assertThat(activityLogger.isActivityLifecycleCallbackCalled(activityLifecycleCallback)); 146 | if (callbacks.contains(activityLifecycleCallback)) { 147 | subject.isTrue(); 148 | } else { 149 | subject.isFalse(); 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/test/java/com/soundcloud/lightcycle/integration_test/AppCompatActivityLoggerTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import com.google.common.truth.BooleanSubject; 9 | import com.soundcloud.lightcycle.integration_test.callback.ActivityLifecycleCallback; 10 | import com.soundcloud.lightcycle.sample.basic.R; 11 | 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.robolectric.Robolectric; 15 | import org.robolectric.Shadows; 16 | import org.robolectric.android.controller.ActivityController; 17 | import org.robolectric.shadows.ShadowActivity; 18 | 19 | import java.util.Arrays; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | import static com.google.common.truth.Truth.assertThat; 24 | 25 | @RunWith(AndroidJUnit4.class) 26 | public class AppCompatActivityLoggerTest { 27 | private final ActivityController controller = Robolectric.buildActivity(SampleAppCompatActivity.class); 28 | private final SampleAppCompatActivity sampleAppCompatActivity = controller.get(); 29 | private final AppCompatActivityLogger appCompatActivityLogger = sampleAppCompatActivity.appCompatActivityLogger; 30 | 31 | @Test 32 | public void onCreate() { 33 | controller.create(); 34 | assertLifecycleCallbackCallIsCorrect( 35 | Collections.singletonList(ActivityLifecycleCallback.onCreate) 36 | ); 37 | } 38 | 39 | @Test 40 | public void onStart() { 41 | controller.create() 42 | .start(); 43 | assertLifecycleCallbackCallIsCorrect( 44 | Arrays.asList(ActivityLifecycleCallback.onCreate, 45 | ActivityLifecycleCallback.onStart) 46 | ); 47 | } 48 | 49 | @Test 50 | public void onRestoreInstance() { 51 | controller.create() 52 | .start() 53 | .restoreInstanceState(new Bundle()); 54 | assertLifecycleCallbackCallIsCorrect( 55 | Arrays.asList(ActivityLifecycleCallback.onCreate, 56 | ActivityLifecycleCallback.onStart, 57 | ActivityLifecycleCallback.onRestoreInstanceState) 58 | ); 59 | } 60 | 61 | @Test 62 | public void onResume() { 63 | controller.create() 64 | .start() 65 | .resume(); 66 | assertLifecycleCallbackCallIsCorrect( 67 | Arrays.asList(ActivityLifecycleCallback.onCreate, 68 | ActivityLifecycleCallback.onStart, 69 | ActivityLifecycleCallback.onResume) 70 | ); 71 | } 72 | 73 | @Test 74 | public void onPause() { 75 | controller.create() 76 | .start() 77 | .resume() 78 | .pause(); 79 | assertLifecycleCallbackCallIsCorrect( 80 | Arrays.asList(ActivityLifecycleCallback.onCreate, 81 | ActivityLifecycleCallback.onStart, 82 | ActivityLifecycleCallback.onResume, 83 | ActivityLifecycleCallback.onPause) 84 | ); 85 | } 86 | 87 | @Test 88 | public void onSaveInstanceState() { 89 | controller.create() 90 | .start() 91 | .resume() 92 | .pause() 93 | .saveInstanceState(new Bundle()); 94 | assertLifecycleCallbackCallIsCorrect( 95 | Arrays.asList(ActivityLifecycleCallback.onCreate, 96 | ActivityLifecycleCallback.onStart, 97 | ActivityLifecycleCallback.onResume, 98 | ActivityLifecycleCallback.onPause, 99 | ActivityLifecycleCallback.onSaveInstanceState) 100 | ); 101 | } 102 | 103 | @Test 104 | public void onStop() { 105 | controller.create() 106 | .start() 107 | .stop(); 108 | assertLifecycleCallbackCallIsCorrect( 109 | Arrays.asList(ActivityLifecycleCallback.onCreate, 110 | ActivityLifecycleCallback.onStart, 111 | ActivityLifecycleCallback.onStop) 112 | ); 113 | } 114 | 115 | @Test 116 | public void onDestroy() { 117 | controller.create() 118 | .destroy(); 119 | assertLifecycleCallbackCallIsCorrect( 120 | Arrays.asList(ActivityLifecycleCallback.onCreate, 121 | ActivityLifecycleCallback.onDestroy) 122 | ); 123 | } 124 | 125 | @Test 126 | public void onNewIntent() { 127 | controller.setup() 128 | .newIntent(new Intent()); 129 | 130 | assertLifecycleCallbackCallIsCorrect( 131 | Arrays.asList(ActivityLifecycleCallback.onCreate, 132 | ActivityLifecycleCallback.onStart, 133 | ActivityLifecycleCallback.onResume, 134 | ActivityLifecycleCallback.onNewIntent) 135 | ); 136 | } 137 | 138 | @Test 139 | public void onOptionsItemSelected() { 140 | controller.setup(); 141 | ShadowActivity shadowActivity = Shadows.shadowOf(controller.get()); 142 | shadowActivity.clickMenuItem(R.id.action_search); 143 | 144 | assertLifecycleCallbackCallIsCorrect( 145 | Arrays.asList(ActivityLifecycleCallback.onCreate, 146 | ActivityLifecycleCallback.onStart, 147 | ActivityLifecycleCallback.onResume, 148 | ActivityLifecycleCallback.onOptionsItemSelected) 149 | ); 150 | } 151 | 152 | private void assertLifecycleCallbackCallIsCorrect(List callbacks) { 153 | for (ActivityLifecycleCallback activityLifecycleCallback : ActivityLifecycleCallback.values()) { 154 | BooleanSubject subject = assertThat(appCompatActivityLogger.isActivityLifecycleCallbackCalled(activityLifecycleCallback)); 155 | if (callbacks.contains(activityLifecycleCallback)) { 156 | subject.isTrue(); 157 | } else { 158 | subject.isFalse(); 159 | } 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/test/java/com/soundcloud/lightcycle/integration_test/FragmentLoggerTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.google.common.truth.BooleanSubject; 6 | import com.soundcloud.lightcycle.integration_test.callback.FragmentLifecycleCallback; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.robolectric.RobolectricTestRunner; 11 | import org.robolectric.android.controller.FragmentController; 12 | import org.robolectric.annotation.Config; 13 | 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | import utils.FragmentTestHelper; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | @RunWith(RobolectricTestRunner.class) 22 | @Config(sdk = 27) 23 | public class FragmentLoggerTest { 24 | private final SampleFragment sampleFragment = new SampleFragment(); 25 | private final FragmentLogger fragmentLogger = sampleFragment.fragmentLogger; 26 | private final FragmentController controller = FragmentController.of(sampleFragment); 27 | 28 | @Test 29 | public void onCreate() { 30 | controller.create(); 31 | assertLifecycleCallbackCallIsCorrect( 32 | Arrays.asList(FragmentLifecycleCallback.onAttach, 33 | FragmentLifecycleCallback.onCreate, 34 | FragmentLifecycleCallback.onViewCreated, 35 | FragmentLifecycleCallback.onActivityCreated) 36 | ); 37 | } 38 | 39 | @Test 40 | public void onStart() { 41 | controller.create() 42 | .start(); 43 | assertLifecycleCallbackCallIsCorrect( 44 | Arrays.asList(FragmentLifecycleCallback.onAttach, 45 | FragmentLifecycleCallback.onCreate, 46 | FragmentLifecycleCallback.onViewCreated, 47 | FragmentLifecycleCallback.onActivityCreated, 48 | FragmentLifecycleCallback.onStart) 49 | ); 50 | } 51 | 52 | @Test 53 | public void onResume() { 54 | controller.create() 55 | .start() 56 | .resume(); 57 | assertLifecycleCallbackCallIsCorrect( 58 | Arrays.asList(FragmentLifecycleCallback.onAttach, 59 | FragmentLifecycleCallback.onCreate, 60 | FragmentLifecycleCallback.onViewCreated, 61 | FragmentLifecycleCallback.onActivityCreated, 62 | FragmentLifecycleCallback.onStart, 63 | FragmentLifecycleCallback.onResume) 64 | ); 65 | } 66 | 67 | @Test 68 | public void onPause() { 69 | controller.create() 70 | .start() 71 | .resume() 72 | .pause(); 73 | assertLifecycleCallbackCallIsCorrect( 74 | Arrays.asList(FragmentLifecycleCallback.onAttach, 75 | FragmentLifecycleCallback.onCreate, 76 | FragmentLifecycleCallback.onViewCreated, 77 | FragmentLifecycleCallback.onActivityCreated, 78 | FragmentLifecycleCallback.onStart, 79 | FragmentLifecycleCallback.onResume, 80 | FragmentLifecycleCallback.onPause) 81 | ); 82 | } 83 | 84 | @Test 85 | public void onSaveInstanceState() { 86 | controller.create() 87 | .start() 88 | .resume() 89 | .pause() 90 | .saveInstanceState(new Bundle()); 91 | assertLifecycleCallbackCallIsCorrect( 92 | Arrays.asList(FragmentLifecycleCallback.onAttach, 93 | FragmentLifecycleCallback.onCreate, 94 | FragmentLifecycleCallback.onViewCreated, 95 | FragmentLifecycleCallback.onActivityCreated, 96 | FragmentLifecycleCallback.onStart, 97 | FragmentLifecycleCallback.onResume, 98 | FragmentLifecycleCallback.onPause, 99 | FragmentLifecycleCallback.onSaveInstanceState) 100 | ); 101 | } 102 | 103 | @Test 104 | public void onStop() { 105 | controller.create() 106 | .start() 107 | .stop(); 108 | 109 | assertLifecycleCallbackCallIsCorrect( 110 | Arrays.asList(FragmentLifecycleCallback.onAttach, 111 | FragmentLifecycleCallback.onCreate, 112 | FragmentLifecycleCallback.onViewCreated, 113 | FragmentLifecycleCallback.onActivityCreated, 114 | FragmentLifecycleCallback.onStart, 115 | FragmentLifecycleCallback.onStop) 116 | ); 117 | } 118 | 119 | @Test 120 | public void onDestroy() { 121 | controller.create() 122 | .destroy(); 123 | assertLifecycleCallbackCallIsCorrect( 124 | Arrays.asList(FragmentLifecycleCallback.onAttach, 125 | FragmentLifecycleCallback.onCreate, 126 | FragmentLifecycleCallback.onViewCreated, 127 | FragmentLifecycleCallback.onActivityCreated, 128 | FragmentLifecycleCallback.onDestroyView, 129 | FragmentLifecycleCallback.onDestroy, 130 | FragmentLifecycleCallback.onDetach) 131 | ); 132 | } 133 | 134 | @Test 135 | public void bindFragmentOnlyOnceWhenReAttched() { 136 | FragmentTestHelper 137 | .from(sampleFragment) 138 | .addFragment() 139 | .removeFragment() 140 | .addFragment(); 141 | 142 | assertThat(sampleFragment.onAttachCount).isEqualTo(2); 143 | assertThat(sampleFragment.bindCount).isEqualTo(1); 144 | } 145 | 146 | private void assertLifecycleCallbackCallIsCorrect(List callbacks) { 147 | for (FragmentLifecycleCallback fragmentLifecycleCallback : FragmentLifecycleCallback.values()) { 148 | BooleanSubject subject = assertThat(fragmentLogger.isFragmentLifecycleCallbackCalled(fragmentLifecycleCallback)); 149 | if (callbacks.contains(fragmentLifecycleCallback)) { 150 | subject.isTrue(); 151 | } else { 152 | subject.isFalse(); 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/test/java/com/soundcloud/lightcycle/integration_test/SampleFragmentDispatcherTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import android.app.Activity; 4 | import android.app.Fragment; 5 | 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static com.google.common.truth.Truth.assertThat; 12 | 13 | @RunWith(AndroidJUnit4.class) 14 | public class SampleFragmentDispatcherTest { 15 | private Fragment fragment = new SampleFragment(); 16 | private Activity activity = new SampleActivity(); 17 | 18 | private SampleFragmentDispatcher dispatcher = new SampleFragmentDispatcher(); 19 | 20 | @Test 21 | public void bindOnlyOnceWhenReAttached() { 22 | dispatcher.onAttach(fragment, activity); 23 | dispatcher.onAttach(fragment, activity); 24 | 25 | assertThat(dispatcher.bindCount).isEqualTo(1); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/test/java/com/soundcloud/lightcycle/integration_test/SampleSupportFragmentDispatcherTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import static com.google.common.truth.Truth.assertThat; 4 | 5 | import com.soundcloud.lightcycle.sample.basic.BuildConfig; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.robolectric.RobolectricTestRunner; 10 | import org.robolectric.annotation.Config; 11 | 12 | import android.app.Activity; 13 | 14 | import androidx.test.ext.junit.runners.AndroidJUnit4; 15 | 16 | @RunWith(AndroidJUnit4.class) 17 | public class SampleSupportFragmentDispatcherTest { 18 | private SampleSupportFragment fragment = new SampleSupportFragment(); 19 | private Activity activity = new SampleAppCompatActivity(); 20 | 21 | private SampleSupportFragmentDispatcher dispatcher = new SampleSupportFragmentDispatcher(); 22 | 23 | @Test 24 | public void bindOnlyOnceWhenReAttached() { 25 | dispatcher.onAttach(fragment, activity); 26 | dispatcher.onAttach(fragment, activity); 27 | 28 | assertThat(dispatcher.bindCount).isEqualTo(1); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/test/java/com/soundcloud/lightcycle/integration_test/SupportFragmentLoggerTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.integration_test; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.fragment.app.testing.FragmentScenario; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import com.google.common.truth.BooleanSubject; 9 | import com.soundcloud.lightcycle.integration_test.callback.FragmentLifecycleCallback; 10 | 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | 18 | import utils.FragmentTestHelper; 19 | 20 | import static com.google.common.truth.Truth.assertThat; 21 | 22 | @RunWith(AndroidJUnit4.class) 23 | public class SupportFragmentLoggerTest { 24 | 25 | private SupportFragmentLogger supportFragmentLogger; 26 | private FragmentScenario controller; 27 | 28 | @Before 29 | public void setUp() { 30 | controller = FragmentScenario.launchInContainer(SampleSupportFragment.class); 31 | controller.onFragment(fragment -> { 32 | supportFragmentLogger = ((SampleSupportFragment) fragment).supportFragmentLogger; 33 | } 34 | ); 35 | } 36 | 37 | @Test 38 | public void onCreate() { 39 | controller.onFragment(fragment -> fragment.onCreate(Bundle.EMPTY)); 40 | assertLifecycleCallbackCallIsCorrect( 41 | Arrays.asList( 42 | FragmentLifecycleCallback.onAttach, 43 | FragmentLifecycleCallback.onCreate, 44 | FragmentLifecycleCallback.onViewCreated, 45 | FragmentLifecycleCallback.onActivityCreated, 46 | FragmentLifecycleCallback.onStart, 47 | FragmentLifecycleCallback.onResume 48 | ) 49 | ); 50 | } 51 | 52 | @Test 53 | public void onStop() { 54 | controller.onFragment(fragment -> { 55 | fragment.onStop(); 56 | }); 57 | assertLifecycleCallbackCallIsCorrect( 58 | Arrays.asList( 59 | FragmentLifecycleCallback.onAttach, 60 | FragmentLifecycleCallback.onCreate, 61 | FragmentLifecycleCallback.onViewCreated, 62 | FragmentLifecycleCallback.onActivityCreated, 63 | FragmentLifecycleCallback.onStart, 64 | FragmentLifecycleCallback.onResume, 65 | FragmentLifecycleCallback.onStop 66 | ) 67 | ); 68 | } 69 | 70 | @Test 71 | public void onDestroy() { 72 | controller.onFragment(fragment -> { 73 | fragment.onDestroy(); 74 | }); 75 | assertLifecycleCallbackCallIsCorrect( 76 | Arrays.asList( 77 | FragmentLifecycleCallback.onAttach, 78 | FragmentLifecycleCallback.onCreate, 79 | FragmentLifecycleCallback.onViewCreated, 80 | FragmentLifecycleCallback.onActivityCreated, 81 | FragmentLifecycleCallback.onStart, 82 | FragmentLifecycleCallback.onResume, 83 | FragmentLifecycleCallback.onDestroy 84 | ) 85 | ); 86 | } 87 | 88 | @Test 89 | public void onDetach() { 90 | controller.onFragment(fragment -> { 91 | fragment.onDetach(); 92 | }); 93 | assertLifecycleCallbackCallIsCorrect( 94 | Arrays.asList( 95 | FragmentLifecycleCallback.onAttach, 96 | FragmentLifecycleCallback.onCreate, 97 | FragmentLifecycleCallback.onViewCreated, 98 | FragmentLifecycleCallback.onActivityCreated, 99 | FragmentLifecycleCallback.onStart, 100 | FragmentLifecycleCallback.onResume, 101 | FragmentLifecycleCallback.onDetach 102 | ) 103 | ); 104 | } 105 | 106 | private void assertLifecycleCallbackCallIsCorrect(List callbacks) { 107 | for (FragmentLifecycleCallback fragmentLifecycleCallback : FragmentLifecycleCallback.values()) { 108 | BooleanSubject subject = assertThat(supportFragmentLogger.isFragmentLifecycleCallbackCalled(fragmentLifecycleCallback)); 109 | if (callbacks.contains(fragmentLifecycleCallback)) { 110 | subject.isTrue(); 111 | } else { 112 | subject.isFalse(); 113 | } 114 | } 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /lightcycle-integration-test/src/test/java/utils/FragmentTestHelper.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import org.robolectric.Robolectric; 4 | 5 | import android.app.Activity; 6 | import android.app.Fragment; 7 | import android.app.FragmentManager; 8 | import android.os.Bundle; 9 | import android.widget.LinearLayout; 10 | 11 | import androidx.fragment.app.FragmentActivity; 12 | 13 | public abstract class FragmentTestHelper { 14 | public abstract FragmentTestHelper removeFragment(); 15 | 16 | public abstract FragmentTestHelper addFragment(); 17 | 18 | public static FragmentTestHelper from(Fragment fragment) { 19 | return SupportFragmentTestHelper.create(fragment); 20 | } 21 | 22 | private static final class SupportFragmentTestHelper extends FragmentTestHelper { 23 | 24 | private final FragmentManager fragmentManager; 25 | private final Fragment fragment; 26 | 27 | SupportFragmentTestHelper(FragmentManager fragmentManager, Fragment fragment) { 28 | this.fragmentManager = fragmentManager; 29 | this.fragment = fragment; 30 | } 31 | 32 | static SupportFragmentTestHelper create(Fragment fragment) { 33 | return new SupportFragmentTestHelper( 34 | Robolectric.buildActivity(SupportFragmentControllerActivity.class) 35 | .create() 36 | .get() 37 | .getFragmentManager(), 38 | fragment); 39 | } 40 | 41 | @Override 42 | public FragmentTestHelper removeFragment() { 43 | fragmentManager 44 | .beginTransaction() 45 | .remove(fragment) 46 | .commit(); 47 | return this; 48 | } 49 | 50 | @Override 51 | public FragmentTestHelper addFragment() { 52 | fragmentManager 53 | .beginTransaction().add(1, fragment) 54 | .commit(); 55 | return this; 56 | } 57 | 58 | static class SupportFragmentControllerActivity extends FragmentActivity { 59 | @Override 60 | protected void onCreate(Bundle savedInstanceState) { 61 | super.onCreate(savedInstanceState); 62 | LinearLayout view = new LinearLayout(this); 63 | view.setId(1); 64 | 65 | setContentView(view); 66 | } 67 | } 68 | } 69 | 70 | private static final class MyFragmentTestHelper extends FragmentTestHelper { 71 | 72 | private final android.app.FragmentManager fragmentManager; 73 | private final android.app.Fragment fragment; 74 | 75 | MyFragmentTestHelper(android.app.FragmentManager fragmentManager, Fragment fragment) { 76 | this.fragmentManager = fragmentManager; 77 | this.fragment = fragment; 78 | } 79 | 80 | static MyFragmentTestHelper create(android.app.Fragment fragment) { 81 | return new MyFragmentTestHelper(Robolectric.buildActivity(FragmentControllerActivity.class) 82 | .create() 83 | .get() 84 | .getFragmentManager(), 85 | fragment); 86 | } 87 | 88 | @Override 89 | public FragmentTestHelper removeFragment() { 90 | fragmentManager 91 | .beginTransaction() 92 | .remove(fragment) 93 | .commit(); 94 | return this; 95 | } 96 | 97 | @Override 98 | public FragmentTestHelper addFragment() { 99 | fragmentManager 100 | .beginTransaction().add(1, fragment) 101 | .commit(); 102 | return this; 103 | } 104 | 105 | public static class FragmentControllerActivity extends Activity { 106 | @Override 107 | protected void onCreate(Bundle savedInstanceState) { 108 | super.onCreate(savedInstanceState); 109 | LinearLayout view = new LinearLayout(this); 110 | view.setId(1); 111 | 112 | setContentView(view); 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lightcycle-lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /lightcycle-lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'checkstyle' 3 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 4 | 5 | android { 6 | compileSdkVersion rootProject.ext.compileSdkVersion 7 | 8 | defaultConfig { 9 | minSdkVersion rootProject.ext.minSdkVersion 10 | targetSdkVersion rootProject.ext.targetSdkVersion 11 | 12 | versionCode 1 13 | versionName "1.0" 14 | } 15 | 16 | buildTypes { 17 | release { 18 | consumerProguardFiles 'proguard-rules.txt' 19 | } 20 | } 21 | 22 | compileOptions { 23 | sourceCompatibility rootProject.ext.sourceCompatibilityVersion 24 | targetCompatibility rootProject.ext.targetCompatibilityVersion 25 | } 26 | 27 | lintOptions { 28 | textReport true 29 | textOutput "stdout" 30 | } 31 | 32 | testOptions { 33 | unitTests.returnDefaultValues = true 34 | } 35 | } 36 | 37 | checkstyle { 38 | configFile rootProject.file('checkstyle.xml') 39 | showViolations true 40 | } 41 | 42 | dependencies { 43 | compileOnly deps.appcompat 44 | 45 | api project(':lightcycle-api') 46 | implementation deps.appcompat 47 | implementation deps.preference 48 | implementation deps.annotations 49 | 50 | testImplementation deps.mockito 51 | testImplementation deps.junit 52 | } 53 | -------------------------------------------------------------------------------- /lightcycle-lib/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=LightCycle library 2 | POM_ARTIFACT_ID=lightcycle-lib 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /lightcycle-lib/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | -keep class com.soundcloud.lightcycle.** { *; } 2 | -dontwarn com.soundcloud.lightcycle.** 3 | -keep class **$LightCycleBinder { *; } 4 | -keep class * implements com.soundcloud.lightcycle.LightCycleDispatcher 5 | -------------------------------------------------------------------------------- /lightcycle-lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lightcycle-lib/src/main/java/com/soundcloud/lightcycle/ActivityLightCycleDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.MenuItem; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | import com.soundcloud.lightcycle.util.Preconditions; 10 | 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | 14 | public class ActivityLightCycleDispatcher 15 | implements LightCycleDispatcher>, ActivityLightCycle { 16 | private final Set> activityLightCycles; 17 | 18 | public ActivityLightCycleDispatcher() { 19 | this.activityLightCycles = new HashSet<>(); 20 | } 21 | 22 | 23 | @Override 24 | public void bind(ActivityLightCycle lightCycle) { 25 | Preconditions.checkBindingTarget(lightCycle); 26 | this.activityLightCycles.add(lightCycle); 27 | } 28 | 29 | @Override 30 | public void onCreate(HostType host, @Nullable Bundle bundle) { 31 | LightCycles.bind(this); 32 | for (ActivityLightCycle component : activityLightCycles) { 33 | component.onCreate(host, bundle); 34 | } 35 | } 36 | 37 | @Override 38 | public void onNewIntent(HostType host, Intent intent) { 39 | for (ActivityLightCycle component : activityLightCycles) { 40 | component.onNewIntent(host, intent); 41 | } 42 | } 43 | 44 | @Override 45 | public void onStart(HostType host) { 46 | for (ActivityLightCycle component : activityLightCycles) { 47 | component.onStart(host); 48 | } 49 | } 50 | 51 | @Override 52 | public void onResume(HostType host) { 53 | for (ActivityLightCycle component : activityLightCycles) { 54 | component.onResume(host); 55 | } 56 | } 57 | 58 | @Override 59 | public boolean onOptionsItemSelected(HostType host, MenuItem item) { 60 | for (ActivityLightCycle component : activityLightCycles) { 61 | if (component.onOptionsItemSelected(host, item)) { 62 | return true; 63 | } 64 | } 65 | return false; 66 | } 67 | 68 | @Override 69 | public void onPause(HostType host) { 70 | for (ActivityLightCycle component : activityLightCycles) { 71 | component.onPause(host); 72 | } 73 | } 74 | 75 | @Override 76 | public void onStop(HostType host) { 77 | for (ActivityLightCycle component : activityLightCycles) { 78 | component.onStop(host); 79 | } 80 | } 81 | 82 | @Override 83 | public void onSaveInstanceState(HostType host, Bundle bundle) { 84 | for (ActivityLightCycle component : activityLightCycles) { 85 | component.onSaveInstanceState(host, bundle); 86 | } 87 | } 88 | 89 | @Override 90 | public void onRestoreInstanceState(HostType host, Bundle bundle) { 91 | for (ActivityLightCycle component : activityLightCycles) { 92 | component.onRestoreInstanceState(host, bundle); 93 | } 94 | } 95 | 96 | @Override 97 | public void onDestroy(HostType host) { 98 | for (ActivityLightCycle component : activityLightCycles) { 99 | component.onDestroy(host); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lightcycle-lib/src/main/java/com/soundcloud/lightcycle/FragmentLightCycleDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.view.MenuItem; 9 | import android.view.View; 10 | 11 | import androidx.annotation.Nullable; 12 | 13 | import com.soundcloud.lightcycle.util.LightCycleBinderHelper; 14 | import com.soundcloud.lightcycle.util.Preconditions; 15 | 16 | import java.util.HashSet; 17 | import java.util.Set; 18 | 19 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 20 | public class FragmentLightCycleDispatcher 21 | implements LightCycleDispatcher>, FragmentLightCycle { 22 | 23 | private final Set> fragmentLightCycles; 24 | private final LightCycleBinderHelper binderHelper; 25 | 26 | public FragmentLightCycleDispatcher() { 27 | this.fragmentLightCycles = new HashSet<>(); 28 | this.binderHelper = new LightCycleBinderHelper(this); 29 | } 30 | 31 | @Override 32 | public void bind(FragmentLightCycle lightCycle) { 33 | Preconditions.checkBindingTarget(lightCycle); 34 | fragmentLightCycles.add(lightCycle); 35 | } 36 | 37 | @Override 38 | public void onAttach(HostType host, Activity activity) { 39 | binderHelper.bindIfNecessary(); 40 | for (FragmentLightCycle component : fragmentLightCycles) { 41 | component.onAttach(host, activity); 42 | } 43 | } 44 | 45 | @Override 46 | public void onAttach(HostType host, Context context) { 47 | binderHelper.bindIfNecessary(); 48 | for (FragmentLightCycle component : fragmentLightCycles) { 49 | component.onAttach(host, context); 50 | } 51 | } 52 | 53 | @Override 54 | public void onCreate(HostType host, @Nullable Bundle bundle) { 55 | for (FragmentLightCycle component : fragmentLightCycles) { 56 | component.onCreate(host, bundle); 57 | } 58 | } 59 | 60 | @Override 61 | public void onViewCreated(HostType host, View view, @Nullable Bundle savedInstanceState) { 62 | for (FragmentLightCycle component : fragmentLightCycles) { 63 | component.onViewCreated(host, view, savedInstanceState); 64 | } 65 | } 66 | 67 | @Override 68 | public void onActivityCreated(HostType host, Bundle bundle) { 69 | for (FragmentLightCycle component : fragmentLightCycles) { 70 | component.onActivityCreated(host, bundle); 71 | } 72 | } 73 | 74 | @Override 75 | public void onStart(HostType host) { 76 | for (FragmentLightCycle component : fragmentLightCycles) { 77 | component.onStart(host); 78 | } 79 | } 80 | 81 | @Override 82 | public void onResume(HostType host) { 83 | for (FragmentLightCycle component : fragmentLightCycles) { 84 | component.onResume(host); 85 | } 86 | } 87 | 88 | @Override 89 | public boolean onOptionsItemSelected(HostType host, MenuItem item) { 90 | for (FragmentLightCycle component : fragmentLightCycles) { 91 | if (component.onOptionsItemSelected(host, item)) { 92 | return true; 93 | } 94 | } 95 | return false; 96 | } 97 | 98 | @Override 99 | public void onPause(HostType host) { 100 | for (FragmentLightCycle component : fragmentLightCycles) { 101 | component.onPause(host); 102 | } 103 | } 104 | 105 | @Override 106 | public void onStop(HostType host) { 107 | for (FragmentLightCycle component : fragmentLightCycles) { 108 | component.onStop(host); 109 | } 110 | } 111 | 112 | @Override 113 | public void onSaveInstanceState(HostType host, Bundle bundle) { 114 | for (FragmentLightCycle component : fragmentLightCycles) { 115 | component.onSaveInstanceState(host, bundle); 116 | } 117 | } 118 | 119 | @Override 120 | public void onDestroyView(HostType host) { 121 | for (FragmentLightCycle component : fragmentLightCycles) { 122 | component.onDestroyView(host); 123 | } 124 | } 125 | 126 | @Override 127 | public void onDestroy(HostType host) { 128 | for (FragmentLightCycle component : fragmentLightCycles) { 129 | component.onDestroy(host); 130 | } 131 | } 132 | 133 | @Override 134 | public void onDetach(HostType host) { 135 | for (FragmentLightCycle component : fragmentLightCycles) { 136 | component.onDetach(host); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /lightcycle-lib/src/main/java/com/soundcloud/lightcycle/LightCycleActivity.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import com.soundcloud.lightcycle.util.Preconditions; 4 | 5 | import android.app.Activity; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.view.MenuItem; 9 | 10 | public abstract class LightCycleActivity 11 | extends Activity 12 | implements LightCycleDispatcher> { 13 | 14 | private final ActivityLightCycleDispatcher lightCycleDispatcher; 15 | 16 | public LightCycleActivity() { 17 | lightCycleDispatcher = new ActivityLightCycleDispatcher<>(); 18 | } 19 | 20 | @Override 21 | public void bind(ActivityLightCycle lightCycle) { 22 | Preconditions.checkBindingTarget(lightCycle); 23 | lightCycleDispatcher.bind(lightCycle); 24 | } 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setActivityContentView(); 30 | LightCycles.bind(this); 31 | lightCycleDispatcher.onCreate(host(), savedInstanceState); 32 | } 33 | 34 | protected abstract void setActivityContentView(); 35 | 36 | @Override 37 | protected void onNewIntent(Intent intent) { 38 | super.onNewIntent(intent); 39 | lightCycleDispatcher.onNewIntent(host(), intent); 40 | } 41 | 42 | @Override 43 | protected void onStart() { 44 | super.onStart(); 45 | lightCycleDispatcher.onStart(host()); 46 | } 47 | 48 | @Override 49 | public boolean onOptionsItemSelected(MenuItem item) { 50 | return lightCycleDispatcher.onOptionsItemSelected(host(), item); 51 | } 52 | 53 | @Override 54 | protected void onStop() { 55 | lightCycleDispatcher.onStop(host()); 56 | super.onStop(); 57 | } 58 | 59 | @Override 60 | protected void onResume() { 61 | super.onResume(); 62 | lightCycleDispatcher.onResume(host()); 63 | } 64 | 65 | @Override 66 | protected void onPause() { 67 | lightCycleDispatcher.onPause(host()); 68 | super.onPause(); 69 | } 70 | 71 | @Override 72 | protected void onSaveInstanceState(Bundle outState) { 73 | super.onSaveInstanceState(outState); 74 | lightCycleDispatcher.onSaveInstanceState(host(), outState); 75 | } 76 | 77 | @Override 78 | protected void onRestoreInstanceState(Bundle savedInstanceState) { 79 | super.onRestoreInstanceState(savedInstanceState); 80 | lightCycleDispatcher.onRestoreInstanceState(host(), savedInstanceState); 81 | } 82 | 83 | @Override 84 | protected void onDestroy() { 85 | lightCycleDispatcher.onDestroy(host()); 86 | super.onDestroy(); 87 | } 88 | 89 | @SuppressWarnings("unchecked") 90 | private HostType host() { 91 | return (HostType) this; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lightcycle-lib/src/main/java/com/soundcloud/lightcycle/LightCycleAppCompatActivity.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import com.soundcloud.lightcycle.util.Preconditions; 4 | 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.view.MenuItem; 8 | 9 | import androidx.appcompat.app.AppCompatActivity; 10 | 11 | public abstract class LightCycleAppCompatActivity 12 | extends AppCompatActivity 13 | implements LightCycleDispatcher> { 14 | 15 | private final ActivityLightCycleDispatcher lightCycleDispatcher; 16 | 17 | public LightCycleAppCompatActivity() { 18 | lightCycleDispatcher = new ActivityLightCycleDispatcher<>(); 19 | } 20 | 21 | @Override 22 | public void bind(ActivityLightCycle lightCycle) { 23 | Preconditions.checkBindingTarget(lightCycle); 24 | lightCycleDispatcher.bind(lightCycle); 25 | } 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setActivityContentView(); 31 | LightCycles.bind(this); 32 | lightCycleDispatcher.onCreate(activity(), savedInstanceState); 33 | } 34 | 35 | protected abstract void setActivityContentView(); 36 | 37 | @Override 38 | protected void onNewIntent(Intent intent) { 39 | super.onNewIntent(intent); 40 | lightCycleDispatcher.onNewIntent(activity(), intent); 41 | } 42 | 43 | @Override 44 | protected void onStart() { 45 | super.onStart(); 46 | lightCycleDispatcher.onStart(activity()); 47 | } 48 | 49 | @Override 50 | public boolean onOptionsItemSelected(MenuItem item) { 51 | return lightCycleDispatcher.onOptionsItemSelected(activity(), item); 52 | } 53 | 54 | @Override 55 | protected void onStop() { 56 | lightCycleDispatcher.onStop(activity()); 57 | super.onStop(); 58 | } 59 | 60 | @Override 61 | protected void onResume() { 62 | super.onResume(); 63 | lightCycleDispatcher.onResume(activity()); 64 | } 65 | 66 | @Override 67 | protected void onPause() { 68 | lightCycleDispatcher.onPause(activity()); 69 | super.onPause(); 70 | } 71 | 72 | @Override 73 | protected void onSaveInstanceState(Bundle outState) { 74 | super.onSaveInstanceState(outState); 75 | lightCycleDispatcher.onSaveInstanceState(activity(), outState); 76 | } 77 | 78 | @Override 79 | protected void onRestoreInstanceState(Bundle savedInstanceState) { 80 | super.onRestoreInstanceState(savedInstanceState); 81 | lightCycleDispatcher.onRestoreInstanceState(activity(), savedInstanceState); 82 | } 83 | 84 | @Override 85 | protected void onDestroy() { 86 | lightCycleDispatcher.onDestroy(activity()); 87 | super.onDestroy(); 88 | } 89 | 90 | @SuppressWarnings("unchecked") 91 | private HostType activity() { 92 | return (HostType) this; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lightcycle-lib/src/main/java/com/soundcloud/lightcycle/LightCycleFragment.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.app.Fragment; 6 | import android.content.Context; 7 | import android.os.Bundle; 8 | import android.view.MenuItem; 9 | import android.view.View; 10 | 11 | import com.soundcloud.lightcycle.util.LightCycleBinderHelper; 12 | import com.soundcloud.lightcycle.util.Preconditions; 13 | 14 | /** 15 | * @deprecated Platform Fragments are deprecated from API level 28. 16 | */ 17 | @Deprecated 18 | public abstract class LightCycleFragment extends Fragment implements LightCycleDispatcher> { 19 | 20 | private final FragmentLightCycleDispatcher lifeCycleDispatcher; 21 | private final LightCycleBinderHelper binderHelper; 22 | 23 | public LightCycleFragment() { 24 | this.lifeCycleDispatcher = new FragmentLightCycleDispatcher<>(); 25 | this.binderHelper = new LightCycleBinderHelper(this); 26 | } 27 | 28 | @Override 29 | public void bind(FragmentLightCycle lifeCycleComponent) { 30 | Preconditions.checkBindingTarget(lifeCycleComponent); 31 | lifeCycleDispatcher.bind(lifeCycleComponent); 32 | } 33 | 34 | @Override 35 | @TargetApi(23) 36 | public void onAttach(Context context) { 37 | super.onAttach(context); 38 | binderHelper.bindIfNecessary(); 39 | lifeCycleDispatcher.onAttach(host(), context); 40 | } 41 | 42 | /* 43 | * Deprecated on API 23 44 | * Use onAttach(Context) instead 45 | */ 46 | @Override 47 | @SuppressWarnings("deprecation") 48 | public void onAttach(Activity activity) { 49 | super.onAttach(activity); 50 | binderHelper.bindIfNecessary(); 51 | lifeCycleDispatcher.onAttach(host(), activity); 52 | } 53 | 54 | @Override 55 | public void onCreate(Bundle savedInstanceState) { 56 | super.onCreate(savedInstanceState); 57 | lifeCycleDispatcher.onCreate(host(), savedInstanceState); 58 | } 59 | 60 | @Override 61 | public void onViewCreated(View view, Bundle savedInstanceState) { 62 | super.onViewCreated(view, savedInstanceState); 63 | lifeCycleDispatcher.onViewCreated(host(), view, savedInstanceState); 64 | } 65 | 66 | @Override 67 | public void onActivityCreated(Bundle savedInstanceState) { 68 | super.onActivityCreated(savedInstanceState); 69 | lifeCycleDispatcher.onActivityCreated(host(), savedInstanceState); 70 | } 71 | 72 | @Override 73 | public void onStart() { 74 | super.onStart(); 75 | lifeCycleDispatcher.onStart(host()); 76 | } 77 | 78 | @Override 79 | public void onResume() { 80 | super.onResume(); 81 | lifeCycleDispatcher.onResume(host()); 82 | } 83 | 84 | @Override 85 | public boolean onOptionsItemSelected(MenuItem item) { 86 | return lifeCycleDispatcher.onOptionsItemSelected(host(), item); 87 | } 88 | 89 | @Override 90 | public void onPause() { 91 | lifeCycleDispatcher.onPause(host()); 92 | super.onPause(); 93 | } 94 | 95 | @Override 96 | public void onStop() { 97 | lifeCycleDispatcher.onStop(host()); 98 | super.onStop(); 99 | } 100 | 101 | @Override 102 | public void onSaveInstanceState(Bundle outState) { 103 | lifeCycleDispatcher.onSaveInstanceState(host(), outState); 104 | super.onSaveInstanceState(outState); 105 | } 106 | 107 | @Override 108 | public void onDestroyView() { 109 | lifeCycleDispatcher.onDestroyView(host()); 110 | super.onDestroyView(); 111 | } 112 | 113 | @Override 114 | public void onDestroy() { 115 | lifeCycleDispatcher.onDestroy(host()); 116 | super.onDestroy(); 117 | } 118 | 119 | @Override 120 | public void onDetach() { 121 | lifeCycleDispatcher.onDetach(host()); 122 | super.onDetach(); 123 | } 124 | 125 | @SuppressWarnings("unchecked") 126 | private HostType host() { 127 | return (HostType) this; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lightcycle-lib/src/main/java/com/soundcloud/lightcycle/LightCyclePreferenceFragmentCompat.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.MenuItem; 6 | import android.view.View; 7 | 8 | import androidx.preference.PreferenceFragmentCompat; 9 | 10 | import com.soundcloud.lightcycle.util.LightCycleBinderHelper; 11 | import com.soundcloud.lightcycle.util.Preconditions; 12 | 13 | public abstract class LightCyclePreferenceFragmentCompat 14 | extends PreferenceFragmentCompat implements LightCycleDispatcher> { 15 | 16 | private final SupportFragmentLightCycleDispatcher lifeCycleDispatcher; 17 | private final LightCycleBinderHelper binderHelper; 18 | 19 | public LightCyclePreferenceFragmentCompat() { 20 | this.lifeCycleDispatcher = new SupportFragmentLightCycleDispatcher<>(); 21 | this.binderHelper = new LightCycleBinderHelper(this); 22 | } 23 | 24 | @Override 25 | public void bind(SupportFragmentLightCycle lifeCycleComponent) { 26 | Preconditions.checkBindingTarget(lifeCycleComponent); 27 | lifeCycleDispatcher.bind(lifeCycleComponent); 28 | } 29 | 30 | @Override 31 | public void onAttach(Activity activity) { 32 | super.onAttach(activity); 33 | binderHelper.bindIfNecessary(); 34 | lifeCycleDispatcher.onAttach(host(), activity); 35 | } 36 | 37 | @Override 38 | public void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | lifeCycleDispatcher.onCreate(host(), savedInstanceState); 41 | } 42 | 43 | @Override 44 | public void onViewCreated(View view, Bundle savedInstanceState) { 45 | super.onViewCreated(view, savedInstanceState); 46 | lifeCycleDispatcher.onViewCreated(host(), view, savedInstanceState); 47 | } 48 | 49 | @Override 50 | public void onActivityCreated(Bundle savedInstanceState) { 51 | super.onActivityCreated(savedInstanceState); 52 | lifeCycleDispatcher.onActivityCreated(host(), savedInstanceState); 53 | } 54 | 55 | @Override 56 | public void onStart() { 57 | super.onStart(); 58 | lifeCycleDispatcher.onStart(host()); 59 | } 60 | 61 | @Override 62 | public void onResume() { 63 | super.onResume(); 64 | lifeCycleDispatcher.onResume(host()); 65 | } 66 | 67 | @Override 68 | public boolean onOptionsItemSelected(MenuItem item) { 69 | return lifeCycleDispatcher.onOptionsItemSelected(host(), item); 70 | } 71 | 72 | @Override 73 | public void onPause() { 74 | lifeCycleDispatcher.onPause(host()); 75 | super.onPause(); 76 | } 77 | 78 | @Override 79 | public void onStop() { 80 | lifeCycleDispatcher.onStop(host()); 81 | super.onStop(); 82 | } 83 | 84 | @Override 85 | public void onSaveInstanceState(Bundle outState) { 86 | lifeCycleDispatcher.onSaveInstanceState(host(), outState); 87 | super.onSaveInstanceState(outState); 88 | } 89 | 90 | @Override 91 | public void onDestroyView() { 92 | lifeCycleDispatcher.onDestroyView(host()); 93 | super.onDestroyView(); 94 | } 95 | 96 | @Override 97 | public void onDestroy() { 98 | lifeCycleDispatcher.onDestroy(host()); 99 | super.onDestroy(); 100 | } 101 | 102 | @Override 103 | public void onDetach() { 104 | lifeCycleDispatcher.onDetach(host()); 105 | super.onDetach(); 106 | } 107 | 108 | @SuppressWarnings("unchecked") 109 | private HostType host() { 110 | return (HostType) this; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /lightcycle-lib/src/main/java/com/soundcloud/lightcycle/LightCycleSupportDialogFragment.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import com.soundcloud.lightcycle.util.LightCycleBinderHelper; 4 | import com.soundcloud.lightcycle.util.Preconditions; 5 | 6 | import android.app.Activity; 7 | import android.os.Bundle; 8 | import android.view.MenuItem; 9 | import android.view.View; 10 | 11 | import androidx.fragment.app.DialogFragment; 12 | 13 | public abstract class LightCycleSupportDialogFragment 14 | extends DialogFragment implements LightCycleDispatcher> { 15 | 16 | private final SupportFragmentLightCycleDispatcher lifeCycleDispatcher; 17 | private final LightCycleBinderHelper binderHelper; 18 | 19 | public LightCycleSupportDialogFragment() { 20 | this.lifeCycleDispatcher = new SupportFragmentLightCycleDispatcher<>(); 21 | this.binderHelper = new LightCycleBinderHelper(this); 22 | } 23 | 24 | @Override 25 | public void bind(SupportFragmentLightCycle lifeCycleComponent) { 26 | Preconditions.checkBindingTarget(lifeCycleComponent); 27 | lifeCycleDispatcher.bind(lifeCycleComponent); 28 | } 29 | 30 | @Override 31 | public void onAttach(Activity activity) { 32 | super.onAttach(activity); 33 | binderHelper.bindIfNecessary(); 34 | lifeCycleDispatcher.onAttach(host(), activity); 35 | } 36 | 37 | @Override 38 | public void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | lifeCycleDispatcher.onCreate(host(), savedInstanceState); 41 | } 42 | 43 | @Override 44 | public void onViewCreated(View view, Bundle savedInstanceState) { 45 | super.onViewCreated(view, savedInstanceState); 46 | lifeCycleDispatcher.onViewCreated(host(), view, savedInstanceState); 47 | } 48 | 49 | @Override 50 | public void onActivityCreated(Bundle savedInstanceState) { 51 | super.onActivityCreated(savedInstanceState); 52 | lifeCycleDispatcher.onActivityCreated(host(), savedInstanceState); 53 | } 54 | 55 | @Override 56 | public void onStart() { 57 | super.onStart(); 58 | lifeCycleDispatcher.onStart(host()); 59 | } 60 | 61 | @Override 62 | public void onResume() { 63 | super.onResume(); 64 | lifeCycleDispatcher.onResume(host()); 65 | } 66 | 67 | @Override 68 | public boolean onOptionsItemSelected(MenuItem item) { 69 | return lifeCycleDispatcher.onOptionsItemSelected(host(), item); 70 | } 71 | 72 | @Override 73 | public void onPause() { 74 | lifeCycleDispatcher.onPause(host()); 75 | super.onPause(); 76 | } 77 | 78 | @Override 79 | public void onStop() { 80 | lifeCycleDispatcher.onStop(host()); 81 | super.onStop(); 82 | } 83 | 84 | @Override 85 | public void onSaveInstanceState(Bundle outState) { 86 | lifeCycleDispatcher.onSaveInstanceState(host(), outState); 87 | super.onSaveInstanceState(outState); 88 | } 89 | 90 | @Override 91 | public void onDestroyView() { 92 | lifeCycleDispatcher.onDestroyView(host()); 93 | super.onDestroyView(); 94 | } 95 | 96 | @Override 97 | public void onDestroy() { 98 | lifeCycleDispatcher.onDestroy(host()); 99 | super.onDestroy(); 100 | } 101 | 102 | @Override 103 | public void onDetach() { 104 | lifeCycleDispatcher.onDetach(host()); 105 | super.onDetach(); 106 | } 107 | 108 | @SuppressWarnings("unchecked") 109 | private HostType host() { 110 | return (HostType) this; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /lightcycle-lib/src/main/java/com/soundcloud/lightcycle/LightCycleSupportFragment.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import com.soundcloud.lightcycle.util.LightCycleBinderHelper; 4 | import com.soundcloud.lightcycle.util.Preconditions; 5 | 6 | import android.app.Activity; 7 | import android.os.Bundle; 8 | import android.view.MenuItem; 9 | import android.view.View; 10 | 11 | import androidx.fragment.app.Fragment; 12 | 13 | public abstract class LightCycleSupportFragment extends Fragment implements LightCycleDispatcher> { 14 | 15 | private final SupportFragmentLightCycleDispatcher lifeCycleDispatcher; 16 | private final LightCycleBinderHelper binderHelper; 17 | 18 | public LightCycleSupportFragment() { 19 | this.lifeCycleDispatcher = new SupportFragmentLightCycleDispatcher<>(); 20 | this.binderHelper = new LightCycleBinderHelper(this); 21 | } 22 | 23 | @Override 24 | public void bind(SupportFragmentLightCycle lifeCycleComponent) { 25 | Preconditions.checkBindingTarget(lifeCycleComponent); 26 | lifeCycleDispatcher.bind(lifeCycleComponent); 27 | } 28 | 29 | @Override 30 | public void onAttach(Activity activity) { 31 | super.onAttach(activity); 32 | binderHelper.bindIfNecessary(); 33 | lifeCycleDispatcher.onAttach(host(), activity); 34 | } 35 | 36 | @Override 37 | public void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | lifeCycleDispatcher.onCreate(host(), savedInstanceState); 40 | } 41 | 42 | @Override 43 | public void onViewCreated(View view, Bundle savedInstanceState) { 44 | super.onViewCreated(view, savedInstanceState); 45 | lifeCycleDispatcher.onViewCreated(host(), view, savedInstanceState); 46 | } 47 | 48 | @Override 49 | public void onActivityCreated(Bundle savedInstanceState) { 50 | super.onActivityCreated(savedInstanceState); 51 | lifeCycleDispatcher.onActivityCreated(host(), savedInstanceState); 52 | } 53 | 54 | @Override 55 | public void onStart() { 56 | super.onStart(); 57 | lifeCycleDispatcher.onStart(host()); 58 | } 59 | 60 | @Override 61 | public void onResume() { 62 | super.onResume(); 63 | lifeCycleDispatcher.onResume(host()); 64 | } 65 | 66 | @Override 67 | public boolean onOptionsItemSelected(MenuItem item) { 68 | return lifeCycleDispatcher.onOptionsItemSelected(host(), item); 69 | } 70 | 71 | @Override 72 | public void onPause() { 73 | lifeCycleDispatcher.onPause(host()); 74 | super.onPause(); 75 | } 76 | 77 | @Override 78 | public void onStop() { 79 | lifeCycleDispatcher.onStop(host()); 80 | super.onStop(); 81 | } 82 | 83 | @Override 84 | public void onSaveInstanceState(Bundle outState) { 85 | lifeCycleDispatcher.onSaveInstanceState(host(), outState); 86 | super.onSaveInstanceState(outState); 87 | } 88 | 89 | @Override 90 | public void onDestroyView() { 91 | lifeCycleDispatcher.onDestroyView(host()); 92 | super.onDestroyView(); 93 | } 94 | 95 | @Override 96 | public void onDestroy() { 97 | lifeCycleDispatcher.onDestroy(host()); 98 | super.onDestroy(); 99 | } 100 | 101 | @Override 102 | public void onDetach() { 103 | lifeCycleDispatcher.onDetach(host()); 104 | super.onDetach(); 105 | } 106 | 107 | @SuppressWarnings("unchecked") 108 | private HostType host() { 109 | return (HostType) this; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lightcycle-lib/src/main/java/com/soundcloud/lightcycle/SupportFragmentLightCycleDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import com.soundcloud.lightcycle.util.LightCycleBinderHelper; 4 | import com.soundcloud.lightcycle.util.Preconditions; 5 | 6 | import android.app.Activity; 7 | import android.os.Bundle; 8 | import android.view.MenuItem; 9 | import android.view.View; 10 | 11 | import androidx.annotation.Nullable; 12 | 13 | import java.util.HashSet; 14 | import java.util.Set; 15 | 16 | public class SupportFragmentLightCycleDispatcher 17 | implements LightCycleDispatcher>, SupportFragmentLightCycle { 18 | 19 | private final Set> fragmentLightCycles; 20 | private final LightCycleBinderHelper binderHelper; 21 | 22 | public SupportFragmentLightCycleDispatcher() { 23 | this.fragmentLightCycles = new HashSet<>(); 24 | this.binderHelper = new LightCycleBinderHelper(this); 25 | } 26 | 27 | @Override 28 | public void bind(SupportFragmentLightCycle lightCycle) { 29 | Preconditions.checkBindingTarget(lightCycle); 30 | fragmentLightCycles.add(lightCycle); 31 | } 32 | 33 | @Override 34 | public void onAttach(HostType host, Activity activity) { 35 | binderHelper.bindIfNecessary(); 36 | for (SupportFragmentLightCycle component : fragmentLightCycles) { 37 | component.onAttach(host, activity); 38 | } 39 | } 40 | 41 | @Override 42 | public void onCreate(HostType host, @Nullable Bundle bundle) { 43 | for (SupportFragmentLightCycle component : fragmentLightCycles) { 44 | component.onCreate(host, bundle); 45 | } 46 | } 47 | 48 | @Override 49 | public void onViewCreated(HostType host, View view, @Nullable Bundle savedInstanceState) { 50 | for (SupportFragmentLightCycle component : fragmentLightCycles) { 51 | component.onViewCreated(host, view, savedInstanceState); 52 | } 53 | } 54 | 55 | @Override 56 | public void onActivityCreated(HostType host, Bundle bundle) { 57 | for (SupportFragmentLightCycle component : fragmentLightCycles) { 58 | component.onActivityCreated(host, bundle); 59 | } 60 | } 61 | 62 | @Override 63 | public void onStart(HostType host) { 64 | for (SupportFragmentLightCycle component : fragmentLightCycles) { 65 | component.onStart(host); 66 | } 67 | } 68 | 69 | @Override 70 | public void onResume(HostType host) { 71 | for (SupportFragmentLightCycle component : fragmentLightCycles) { 72 | component.onResume(host); 73 | } 74 | } 75 | 76 | @Override 77 | public boolean onOptionsItemSelected(HostType host, MenuItem item) { 78 | for (SupportFragmentLightCycle component : fragmentLightCycles) { 79 | if (component.onOptionsItemSelected(host, item)) { 80 | return true; 81 | } 82 | } 83 | return false; 84 | } 85 | 86 | @Override 87 | public void onPause(HostType host) { 88 | for (SupportFragmentLightCycle component : fragmentLightCycles) { 89 | component.onPause(host); 90 | } 91 | } 92 | 93 | @Override 94 | public void onStop(HostType host) { 95 | for (SupportFragmentLightCycle component : fragmentLightCycles) { 96 | component.onStop(host); 97 | } 98 | } 99 | 100 | @Override 101 | public void onSaveInstanceState(HostType host, Bundle bundle) { 102 | for (SupportFragmentLightCycle component : fragmentLightCycles) { 103 | component.onSaveInstanceState(host, bundle); 104 | } 105 | } 106 | 107 | @Override 108 | public void onDestroyView(HostType host) { 109 | for (SupportFragmentLightCycle component : fragmentLightCycles) { 110 | component.onDestroyView(host); 111 | } 112 | } 113 | 114 | @Override 115 | public void onDestroy(HostType host) { 116 | for (SupportFragmentLightCycle component : fragmentLightCycles) { 117 | component.onDestroy(host); 118 | } 119 | } 120 | 121 | @Override 122 | public void onDetach(HostType host) { 123 | for (SupportFragmentLightCycle component : fragmentLightCycles) { 124 | component.onDetach(host); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /lightcycle-lib/src/main/java/com/soundcloud/lightcycle/util/LightCycleBinderHelper.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.util; 2 | 3 | import com.soundcloud.lightcycle.LightCycleDispatcher; 4 | import com.soundcloud.lightcycle.LightCycles; 5 | 6 | public class LightCycleBinderHelper { 7 | 8 | private final LightCycleDispatcher dispatcher; 9 | private boolean isBound = false; 10 | 11 | public LightCycleBinderHelper(LightCycleDispatcher dispatcher) { 12 | this.dispatcher = dispatcher; 13 | } 14 | 15 | public void bindIfNecessary() { 16 | if (!isBound) { 17 | LightCycles.bind(dispatcher); 18 | isBound = true; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lightcycle-lib/src/main/java/com/soundcloud/lightcycle/util/Preconditions.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle.util; 2 | 3 | import com.soundcloud.lightcycle.ActivityLightCycle; 4 | import com.soundcloud.lightcycle.FragmentLightCycle; 5 | import com.soundcloud.lightcycle.SupportFragmentLightCycle; 6 | 7 | public final class Preconditions { 8 | 9 | private static final String BINDING_TARGET_NULL_ERROR_MESSAGE = "Binding target must not be null"; 10 | 11 | public static void checkBindingTarget(final ActivityLightCycle activityLightCycle) { 12 | if (activityLightCycle == null) { 13 | throw new NullPointerException(BINDING_TARGET_NULL_ERROR_MESSAGE); 14 | } 15 | } 16 | 17 | public static void checkBindingTarget(final FragmentLightCycle fragmentLightCycle) { 18 | if (fragmentLightCycle == null) { 19 | throw new NullPointerException(BINDING_TARGET_NULL_ERROR_MESSAGE); 20 | } 21 | } 22 | 23 | public static void checkBindingTarget(final SupportFragmentLightCycle supportFragmentLightCycle) { 24 | if (supportFragmentLightCycle == null) { 25 | throw new NullPointerException(BINDING_TARGET_NULL_ERROR_MESSAGE); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lightcycle-lib/src/test/java/com/soundcloud/lightcycle/ActivityLightCycleDispatcherTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | 7 | import androidx.fragment.app.FragmentActivity; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.mockito.Mock; 13 | import org.mockito.runners.MockitoJUnitRunner; 14 | 15 | import static org.mockito.Mockito.mock; 16 | import static org.mockito.Mockito.times; 17 | import static org.mockito.Mockito.verify; 18 | 19 | @RunWith(MockitoJUnitRunner.class) 20 | public class ActivityLightCycleDispatcherTest { 21 | @Mock private ActivityLightCycle lightCycleComponent1; 22 | @Mock private ActivityLightCycle lightCycleComponent2; 23 | @Mock private FragmentActivity activity; 24 | private ActivityLightCycleDispatcher dispatcher; 25 | 26 | @Before 27 | public void setUp() throws Exception { 28 | dispatcher = new ActivityLightCycleDispatcher<>(); 29 | dispatcher.bind(lightCycleComponent1); 30 | dispatcher.bind(lightCycleComponent2); 31 | } 32 | 33 | @Test 34 | public void dispatchOnCreate() { 35 | final Bundle bundle = Bundle.EMPTY; 36 | 37 | dispatcher.onCreate(activity, bundle); 38 | 39 | verify(lightCycleComponent1).onCreate(activity, bundle); 40 | verify(lightCycleComponent2).onCreate(activity, bundle); 41 | } 42 | 43 | @Test 44 | public void dispatchOnNewIntent() { 45 | final Intent intent = mock(Intent.class); 46 | 47 | dispatcher.onNewIntent(activity, intent); 48 | 49 | verify(lightCycleComponent1).onNewIntent(activity, intent); 50 | verify(lightCycleComponent2).onNewIntent(activity, intent); 51 | } 52 | 53 | @Test 54 | public void dispatchOnStart() { 55 | dispatcher.onStart(activity); 56 | 57 | verify(lightCycleComponent1).onStart(activity); 58 | verify(lightCycleComponent2).onStart(activity); 59 | } 60 | 61 | @Test 62 | public void dispatchOnResume() { 63 | dispatcher.onResume(activity); 64 | 65 | verify(lightCycleComponent1).onResume(activity); 66 | verify(lightCycleComponent2).onResume(activity); 67 | } 68 | 69 | @Test 70 | public void dispatchOnPause() { 71 | dispatcher.onPause(activity); 72 | 73 | verify(lightCycleComponent1).onPause(activity); 74 | verify(lightCycleComponent2).onPause(activity); 75 | } 76 | 77 | @Test 78 | public void dispatchOnStop() { 79 | dispatcher.onStop(activity); 80 | 81 | verify(lightCycleComponent1).onStop(activity); 82 | verify(lightCycleComponent2).onStop(activity); 83 | } 84 | 85 | @Test 86 | public void dispatchOnSaveInstanceState() { 87 | final Bundle bundle = Bundle.EMPTY; 88 | 89 | dispatcher.onSaveInstanceState(activity, bundle); 90 | 91 | verify(lightCycleComponent1).onSaveInstanceState(activity, bundle); 92 | verify(lightCycleComponent2).onSaveInstanceState(activity, bundle); 93 | } 94 | 95 | @Test 96 | public void dispatchOnRestoreInstanceState() { 97 | final Bundle bundle = Bundle.EMPTY; 98 | 99 | dispatcher.onRestoreInstanceState(activity, bundle); 100 | 101 | verify(lightCycleComponent1).onRestoreInstanceState(activity, bundle); 102 | verify(lightCycleComponent2).onRestoreInstanceState(activity, bundle); 103 | } 104 | 105 | @Test 106 | public void dispatchOnDestroy() { 107 | dispatcher.onDestroy(activity); 108 | 109 | verify(lightCycleComponent1).onDestroy(activity); 110 | verify(lightCycleComponent2).onDestroy(activity); 111 | } 112 | 113 | @Test 114 | public void dispatchOnlyOnceToDuplicatesComponents() { 115 | final Bundle bundle = Bundle.EMPTY; 116 | dispatcher.bind(lightCycleComponent1); 117 | dispatcher.bind(lightCycleComponent1); 118 | dispatcher.onCreate(activity, bundle); 119 | 120 | verify(lightCycleComponent1, times(1)).onCreate(activity, bundle); 121 | } 122 | 123 | @Test(expected = NullPointerException.class) 124 | public void nullBinderTarget() { 125 | dispatcher.bind(null); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /lightcycle-lib/src/test/java/com/soundcloud/lightcycle/FragmentLightCycleDispatcherTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import static org.mockito.Mockito.mock; 4 | import static org.mockito.Mockito.verify; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.Mock; 10 | import org.mockito.runners.MockitoJUnitRunner; 11 | 12 | import android.app.Activity; 13 | import android.app.Fragment; 14 | import android.content.Context; 15 | import android.os.Bundle; 16 | import android.view.View; 17 | 18 | @RunWith(MockitoJUnitRunner.class) 19 | public class FragmentLightCycleDispatcherTest { 20 | @Mock private FragmentLightCycle lifeCycleComponent1; 21 | @Mock private FragmentLightCycle lifeCycleComponent2; 22 | @Mock private Fragment fragment; 23 | @Mock private Activity activity; 24 | @Mock private Context context; 25 | private FragmentLightCycleDispatcher dispatcher; 26 | 27 | @Before 28 | public void setUp() throws Exception { 29 | dispatcher = new FragmentLightCycleDispatcher<>(); 30 | dispatcher.bind(lifeCycleComponent1); 31 | dispatcher.bind(lifeCycleComponent2); 32 | } 33 | 34 | @Test 35 | public void shouldNotifyOnAttach() { 36 | dispatcher.onAttach(fragment, activity); 37 | 38 | verify(lifeCycleComponent1).onAttach(fragment, activity); 39 | verify(lifeCycleComponent2).onAttach(fragment, activity); 40 | } 41 | 42 | @Test 43 | public void shouldNotifyOnAttachAPI23() { 44 | dispatcher.onAttach(fragment, context); 45 | 46 | verify(lifeCycleComponent1).onAttach(fragment, context); 47 | verify(lifeCycleComponent2).onAttach(fragment, context); 48 | } 49 | 50 | @Test 51 | public void shouldNotifyOnCreate() { 52 | final Bundle bundle = Bundle.EMPTY; 53 | 54 | dispatcher.onCreate(fragment, bundle); 55 | 56 | verify(lifeCycleComponent1).onCreate(fragment, bundle); 57 | verify(lifeCycleComponent2).onCreate(fragment, bundle); 58 | } 59 | 60 | @Test 61 | public void shouldNotifyOnStart() { 62 | dispatcher.onStart(fragment); 63 | 64 | verify(lifeCycleComponent1).onStart(fragment); 65 | verify(lifeCycleComponent2).onStart(fragment); 66 | } 67 | 68 | @Test 69 | public void shouldNotifyOnResume() { 70 | dispatcher.onResume(fragment); 71 | 72 | verify(lifeCycleComponent1).onResume(fragment); 73 | verify(lifeCycleComponent2).onResume(fragment); 74 | } 75 | 76 | @Test 77 | public void shouldNotifyOnPause() { 78 | dispatcher.onPause(fragment); 79 | 80 | verify(lifeCycleComponent1).onPause(fragment); 81 | verify(lifeCycleComponent2).onPause(fragment); 82 | } 83 | 84 | @Test 85 | public void shouldNotifyOnStop() { 86 | dispatcher.onStop(fragment); 87 | 88 | verify(lifeCycleComponent1).onStop(fragment); 89 | verify(lifeCycleComponent2).onStop(fragment); 90 | } 91 | 92 | @Test 93 | public void shouldNotifyOnActivityCreated() { 94 | final Bundle bundle = Bundle.EMPTY; 95 | 96 | dispatcher.onActivityCreated(fragment, bundle); 97 | 98 | verify(lifeCycleComponent1).onActivityCreated(fragment, bundle); 99 | verify(lifeCycleComponent2).onActivityCreated(fragment, bundle); 100 | } 101 | 102 | @Test 103 | public void shouldNotifyOnSaveInstanceState() { 104 | final Bundle bundle = Bundle.EMPTY; 105 | 106 | dispatcher.onSaveInstanceState(fragment, bundle); 107 | 108 | verify(lifeCycleComponent1).onSaveInstanceState(fragment, bundle); 109 | verify(lifeCycleComponent2).onSaveInstanceState(fragment, bundle); 110 | } 111 | 112 | @Test 113 | public void shouldNotifyOnDestroy() { 114 | dispatcher.onDestroy(fragment); 115 | 116 | verify(lifeCycleComponent1).onDestroy(fragment); 117 | verify(lifeCycleComponent2).onDestroy(fragment); 118 | } 119 | 120 | @Test 121 | public void shouldNotifyOnDetach() { 122 | dispatcher.onDetach(fragment); 123 | 124 | verify(lifeCycleComponent1).onDetach(fragment); 125 | verify(lifeCycleComponent2).onDetach(fragment); 126 | } 127 | 128 | @Test 129 | public void shouldNotifyOnViewCreated() { 130 | final Bundle bundle = Bundle.EMPTY; 131 | final View view = mock(View.class); 132 | 133 | dispatcher.onViewCreated(fragment, view, bundle); 134 | 135 | verify(lifeCycleComponent1).onViewCreated(fragment, view, bundle); 136 | verify(lifeCycleComponent2).onViewCreated(fragment, view, bundle); 137 | } 138 | 139 | @Test 140 | public void shouldNotifyOnDestroyView() { 141 | dispatcher.onDestroyView(fragment); 142 | 143 | verify(lifeCycleComponent1).onDestroyView(fragment); 144 | verify(lifeCycleComponent2).onDestroyView(fragment); 145 | } 146 | 147 | @Test(expected = NullPointerException.class) 148 | public void nullBinderTarget() { 149 | dispatcher.bind(null); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /lightcycle-lib/src/test/java/com/soundcloud/lightcycle/SupportFragmentLightCycleDispatcherTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import static org.mockito.Mockito.mock; 4 | import static org.mockito.Mockito.verify; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.Mock; 10 | import org.mockito.runners.MockitoJUnitRunner; 11 | 12 | import android.app.Activity; 13 | import android.os.Bundle; 14 | import android.view.View; 15 | 16 | import androidx.fragment.app.Fragment; 17 | 18 | @RunWith(MockitoJUnitRunner.class) 19 | public class SupportFragmentLightCycleDispatcherTest { 20 | @Mock private SupportFragmentLightCycle lifeCycleComponent1; 21 | @Mock private SupportFragmentLightCycle lifeCycleComponent2; 22 | @Mock private Fragment fragment; 23 | @Mock private Activity activity; 24 | private SupportFragmentLightCycleDispatcher dispatcher; 25 | 26 | @Before 27 | public void setUp() throws Exception { 28 | dispatcher = new SupportFragmentLightCycleDispatcher<>(); 29 | dispatcher.bind(lifeCycleComponent1); 30 | dispatcher.bind(lifeCycleComponent2); 31 | } 32 | 33 | @Test 34 | public void shouldNotifyOnAttach() { 35 | dispatcher.onAttach(fragment, activity); 36 | 37 | verify(lifeCycleComponent1).onAttach(fragment, activity); 38 | verify(lifeCycleComponent2).onAttach(fragment, activity); 39 | } 40 | 41 | @Test 42 | public void shouldNotifyOnCreate() { 43 | final Bundle bundle = Bundle.EMPTY; 44 | 45 | dispatcher.onCreate(fragment, bundle); 46 | 47 | verify(lifeCycleComponent1).onCreate(fragment, bundle); 48 | verify(lifeCycleComponent2).onCreate(fragment, bundle); 49 | } 50 | 51 | @Test 52 | public void shouldNotifyOnStart() { 53 | dispatcher.onStart(fragment); 54 | 55 | verify(lifeCycleComponent1).onStart(fragment); 56 | verify(lifeCycleComponent2).onStart(fragment); 57 | } 58 | 59 | @Test 60 | public void shouldNotifyOnResume() { 61 | dispatcher.onResume(fragment); 62 | 63 | verify(lifeCycleComponent1).onResume(fragment); 64 | verify(lifeCycleComponent2).onResume(fragment); 65 | } 66 | 67 | @Test 68 | public void shouldNotifyOnPause() { 69 | dispatcher.onPause(fragment); 70 | 71 | verify(lifeCycleComponent1).onPause(fragment); 72 | verify(lifeCycleComponent2).onPause(fragment); 73 | } 74 | 75 | @Test 76 | public void shouldNotifyOnStop() { 77 | dispatcher.onStop(fragment); 78 | 79 | verify(lifeCycleComponent1).onStop(fragment); 80 | verify(lifeCycleComponent2).onStop(fragment); 81 | } 82 | 83 | @Test 84 | public void shouldNotifyOnSaveInstanceState() { 85 | final Bundle bundle = Bundle.EMPTY; 86 | 87 | dispatcher.onSaveInstanceState(fragment, bundle); 88 | 89 | verify(lifeCycleComponent1).onSaveInstanceState(fragment, bundle); 90 | verify(lifeCycleComponent2).onSaveInstanceState(fragment, bundle); 91 | } 92 | 93 | @Test 94 | public void shouldNotifyOnActivityCreated() { 95 | final Bundle bundle = Bundle.EMPTY; 96 | 97 | dispatcher.onActivityCreated(fragment, bundle); 98 | 99 | verify(lifeCycleComponent1).onActivityCreated(fragment, bundle); 100 | verify(lifeCycleComponent2).onActivityCreated(fragment, bundle); 101 | } 102 | 103 | @Test 104 | public void shouldNotifyOnDestroy() { 105 | dispatcher.onDestroy(fragment); 106 | 107 | verify(lifeCycleComponent1).onDestroy(fragment); 108 | verify(lifeCycleComponent2).onDestroy(fragment); 109 | } 110 | 111 | @Test 112 | public void shouldNotifyOnDetach() { 113 | dispatcher.onDetach(fragment); 114 | 115 | verify(lifeCycleComponent1).onDetach(fragment); 116 | verify(lifeCycleComponent2).onDetach(fragment); 117 | } 118 | 119 | @Test 120 | public void shouldNotifyOnViewCreated() { 121 | final Bundle bundle = Bundle.EMPTY; 122 | final View view = mock(View.class); 123 | 124 | dispatcher.onViewCreated(fragment, view, bundle); 125 | 126 | verify(lifeCycleComponent1).onViewCreated(fragment, view, bundle); 127 | verify(lifeCycleComponent2).onViewCreated(fragment, view, bundle); 128 | } 129 | 130 | @Test 131 | public void shouldNotifyOnDestroyView() { 132 | dispatcher.onDestroyView(fragment); 133 | 134 | verify(lifeCycleComponent1).onDestroyView(fragment); 135 | verify(lifeCycleComponent2).onDestroyView(fragment); 136 | } 137 | 138 | @Test(expected = NullPointerException.class) 139 | public void nullBinderTarget() { 140 | dispatcher.bind(null); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lightcycle-processor-tests/build.gradle: -------------------------------------------------------------------------------- 1 | import org.gradle.internal.jvm.Jvm 2 | 3 | apply plugin: 'com.android.library' 4 | apply plugin: 'checkstyle' 5 | 6 | android { 7 | compileSdkVersion rootProject.ext.compileSdkVersion 8 | 9 | defaultConfig { 10 | minSdkVersion rootProject.ext.minSdkVersion 11 | targetSdkVersion rootProject.ext.targetSdkVersion 12 | 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true // this is required for having an annotation processor in the compiling classpath 17 | } 18 | 19 | compileOptions { 20 | sourceCompatibility rootProject.ext.sourceCompatibilityVersion 21 | targetCompatibility rootProject.ext.targetCompatibilityVersion 22 | } 23 | 24 | testOptions { 25 | unitTests.returnDefaultValues = true 26 | } 27 | } 28 | 29 | checkstyle { 30 | configFile rootProject.file('checkstyle.xml') 31 | showViolations true 32 | } 33 | 34 | dependencies { 35 | testImplementation project(':lightcycle-processor') 36 | testImplementation deps.javapoet 37 | 38 | testImplementation deps.android 39 | testImplementation deps.appcompat 40 | testImplementation deps.compiletesting 41 | 42 | testImplementation files(getRuntimeJar()) 43 | testImplementation files(Jvm.current().getToolsJar()) 44 | } 45 | 46 | // Taken from: 47 | // https://github.com/JakeWharton/butterknife/blob/bac9b737fdc7e37a30cd8407289e626bd5a161bc/butterknife-runtime/build.gradle 48 | static def getRuntimeJar() { 49 | try { 50 | final File javaBase = new File(System.getProperty("java.home")).getCanonicalFile(); 51 | File runtimeJar = new File(javaBase, "lib/rt.jar"); 52 | if (runtimeJar.exists()) { 53 | return runtimeJar; 54 | } 55 | runtimeJar = new File(javaBase, "jre/lib/rt.jar"); 56 | return runtimeJar.exists() ? runtimeJar : null; 57 | } catch (IOException e) { 58 | throw new RuntimeException(e); 59 | } 60 | } -------------------------------------------------------------------------------- /lightcycle-processor-tests/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lightcycle-processor-tests/src/test/java/com/soundcloud/lightcycle/FragmentInjectionTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import com.google.common.base.Joiner; 4 | import com.google.common.collect.ImmutableList; 5 | import com.google.common.truth.Truth; 6 | import com.google.testing.compile.JavaFileObjects; 7 | import com.google.testing.compile.JavaSourceSubjectFactory; 8 | import com.google.testing.compile.JavaSourcesSubjectFactory; 9 | 10 | import org.junit.Test; 11 | 12 | import javax.tools.JavaFileObject; 13 | 14 | public class FragmentInjectionTest { 15 | 16 | @Test 17 | public void shouldGenerateInjectorForFragment() { 18 | JavaFileObject validTestFragment = JavaFileObjects.forSourceString("com/test/ValidTestFragment", Joiner.on("\n").join( 19 | "package com.test;", 20 | "", 21 | "import com.soundcloud.lightcycle.FragmentLightCycle;", 22 | "import com.soundcloud.lightcycle.LightCycle;", 23 | "import com.soundcloud.lightcycle.LightCycleDispatcher;", 24 | "import com.soundcloud.lightcycle.DefaultFragmentLightCycle;", 25 | "", 26 | "import android.app.Fragment;", 27 | "", 28 | "public class ValidTestFragment extends Fragment ", 29 | " implements LightCycleDispatcher> {", 30 | "", 31 | " @LightCycle LightCycle1 lightCycle1;", 32 | " @LightCycle LightCycle2 lightCycle2;", 33 | "", 34 | " @Override", 35 | " public void bind(FragmentLightCycle lightCycle) {", 36 | " // nop", 37 | " }", 38 | "", 39 | "}", 40 | "", 41 | "class LightCycle1 extends DefaultFragmentLightCycle {", 42 | "}", 43 | "", 44 | "class LightCycle2 extends DefaultFragmentLightCycle {", 45 | "}")); 46 | 47 | JavaFileObject expectedSource = JavaFileObjects.forSourceString("com.test.ValidTestFragment$LightCycleBinder", Joiner.on("\n").join( 48 | "package com.test;", 49 | "", 50 | "public final class ValidTestFragment$LightCycleBinder {", 51 | "", 52 | " public static void bind(ValidTestFragment target) {", 53 | " final com.soundcloud.lightcycle.FragmentLightCycle lightCycle1$Lifted = ", 54 | " com.soundcloud.lightcycle.LightCycles.lift(target.lightCycle1);", 55 | " target.bind(lightCycle1$Lifted);", 56 | " final com.soundcloud.lightcycle.FragmentLightCycle lightCycle2$Lifted = ", 57 | " com.soundcloud.lightcycle.LightCycles.lift(target.lightCycle2);", 58 | " target.bind(lightCycle2$Lifted);", 59 | " }", 60 | "}")); 61 | 62 | Truth.assertAbout(JavaSourceSubjectFactory.javaSource()) 63 | .that(validTestFragment) 64 | .processedWith(new LightCycleProcessor()) 65 | .compilesWithoutError() 66 | .and().generatesSources(expectedSource); 67 | } 68 | 69 | @Test 70 | public void shouldGenerateInjectorForLightCycleFragment() { 71 | JavaFileObject validTestLightCycleFragment = JavaFileObjects.forSourceString("com/test/ValidTestLightCycleFragment", Joiner.on("\n").join( 72 | "package com.test;", 73 | "", 74 | "import com.soundcloud.lightcycle.DefaultFragmentLightCycle;", 75 | "import com.soundcloud.lightcycle.LightCycle;", 76 | "import com.soundcloud.lightcycle.LightCycleDispatcher;", 77 | "import com.soundcloud.lightcycle.LightCycleFragment;", 78 | "", 79 | "import android.app.Activity;", 80 | "", 81 | "public class ValidTestLightCycleFragment ", 82 | " extends LightCycleFragment {", 83 | " @LightCycle LightCycle1 lightCycle1;", 84 | " @LightCycle LightCycle2 lightCycle2;", 85 | "", 86 | "}", 87 | "", 88 | "class LightCycle1 extends DefaultFragmentLightCycle {", 89 | "}", 90 | "", 91 | "class LightCycle2 extends DefaultFragmentLightCycle {", 92 | "}")); 93 | 94 | // Because neither the processor or the lib (java libraries) can depend on the api (Android library), 95 | // we have to create a fake `LightCycleFragment` here for testing purpose. 96 | JavaFileObject fakeLightCycleFragment = JavaFileObjects.forSourceString("com/soundcloud/lightcycle/LightCycleFragment", Joiner.on("\n").join( 97 | "package com.soundcloud.lightcycle;", 98 | "", 99 | "import android.app.Fragment;", 100 | "", 101 | "public abstract class LightCycleFragment", 102 | " extends Fragment", 103 | " implements LightCycleDispatcher> {", 104 | "", 105 | " @Override", 106 | " public void bind(FragmentLightCycle lifeCycleComponent) { }", 107 | "", 108 | "}")); 109 | 110 | JavaFileObject expectedSource = JavaFileObjects.forSourceString("com.test.ValidTestLightCycleFragment$LightCycleBinder", Joiner.on("\n").join( 111 | "package com.test;", 112 | "", 113 | "public final class ValidTestLightCycleFragment$LightCycleBinder {", 114 | "", 115 | " public static void bind(ValidTestLightCycleFragment target) {", 116 | " final com.soundcloud.lightcycle.FragmentLightCycle lightCycle1$Lifted = ", 117 | " com.soundcloud.lightcycle.LightCycles.lift(target.lightCycle1);", 118 | " target.bind(lightCycle1$Lifted);", 119 | " final com.soundcloud.lightcycle.FragmentLightCycle lightCycle2$Lifted = ", 120 | " com.soundcloud.lightcycle.LightCycles.lift(target.lightCycle2);", 121 | " target.bind(lightCycle2$Lifted);", 122 | " }", 123 | "}")); 124 | 125 | Truth.assertAbout(JavaSourcesSubjectFactory.javaSources()) 126 | .that(ImmutableList.of(validTestLightCycleFragment, fakeLightCycleFragment)) 127 | .processedWith(new LightCycleProcessor()) 128 | .compilesWithoutError() 129 | .and().generatesSources(expectedSource); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lightcycle-processor-tests/src/test/java/com/soundcloud/lightcycle/InvalidCaseTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import com.google.common.base.Joiner; 4 | import com.google.common.truth.Truth; 5 | import com.google.testing.compile.JavaFileObjects; 6 | import com.google.testing.compile.JavaSourceSubjectFactory; 7 | 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.junit.rules.ExpectedException; 11 | 12 | import javax.tools.JavaFileObject; 13 | 14 | public class InvalidCaseTest { 15 | 16 | @Rule 17 | public ExpectedException expectedException = ExpectedException.none(); 18 | 19 | @Test 20 | public void shouldThrowExceptionWhenLightCycleFieldIsPrivate() { 21 | expectedException.expect(RuntimeException.class); 22 | expectedException.expectMessage("Annotated fields cannot be private: " 23 | + "com.test.PrivateFieldsTestFragment#lightCycle1(com.test.LightCycle1)"); 24 | 25 | JavaFileObject privateFieldsTestFragment = JavaFileObjects.forSourceString("com/test/PrivateFieldsTestFragment", Joiner.on("\n").join( 26 | "package com.test;", 27 | "", 28 | "import com.soundcloud.lightcycle.DefaultSupportFragmentLightCycle;", 29 | "import com.soundcloud.lightcycle.LightCycle;", 30 | "import com.soundcloud.lightcycle.LightCycleDispatcher;", 31 | "import com.soundcloud.lightcycle.SupportFragmentLightCycle;", 32 | "", 33 | "import androidx.fragment.app.Fragment;", 34 | "", 35 | "public class PrivateFieldsTestFragment extends Fragment ", 36 | " implements LightCycleDispatcher {", 37 | "", 38 | " private @LightCycle LightCycle1 lightCycle1;", 39 | " private @LightCycle LightCycle2 lightCycle2;", 40 | "", 41 | " @Override", 42 | " public void bind(SupportFragmentLightCycle lightCycle) {", 43 | " // nop", 44 | " }", 45 | "", 46 | "}", 47 | "", 48 | "class LightCycle1 extends DefaultSupportFragmentLightCycle {", 49 | "}", 50 | "", 51 | "class LightCycle2 extends DefaultSupportFragmentLightCycle {", 52 | "}")); 53 | 54 | Truth.assertAbout(JavaSourceSubjectFactory.javaSource()) 55 | .that(privateFieldsTestFragment) 56 | .processedWith(new LightCycleProcessor()) 57 | .compilesWithoutError(); 58 | } 59 | 60 | @Test 61 | public void lightCycleFieldIsNotALightCycle() { 62 | JavaFileObject privateFieldsTestFragment = JavaFileObjects.forSourceString("com/test/FieldsNotLightCyclesTestFragment", Joiner.on("\n").join( 63 | "package com.test;", 64 | "", 65 | "import com.soundcloud.lightcycle.DefaultSupportFragmentLightCycle;", 66 | "import com.soundcloud.lightcycle.LightCycle;", 67 | "import com.soundcloud.lightcycle.LightCycleDispatcher;", 68 | "import com.soundcloud.lightcycle.SupportFragmentLightCycle;", 69 | "", 70 | "import android.app.Fragment;", 71 | "", 72 | "public class FieldsNotLightCyclesTestFragment extends Fragment ", 73 | " implements LightCycleDispatcher {", 74 | "", 75 | " @LightCycle LightCycle1 lightCycle1;", 76 | " @LightCycle LightCycle2 lightCycle2;", 77 | "", 78 | " @Override", 79 | " public void bind(SupportFragmentLightCycle lightCycle) {", 80 | " // nop", 81 | " }", 82 | "", 83 | "}", 84 | "", 85 | "class LightCycle1 {", 86 | "}", 87 | "", 88 | "class LightCycle2 {", 89 | "}")); 90 | 91 | Truth.assertAbout(JavaSourceSubjectFactory.javaSource()) 92 | .that(privateFieldsTestFragment) 93 | .processedWith(new LightCycleProcessor()) 94 | .failsToCompile() 95 | .withErrorContaining("no suitable method found for lift(com.test.LightCycle1)"); 96 | } 97 | 98 | @Test 99 | public void missingGenericTestActivity() { 100 | JavaFileObject missingGenericTestActivity = JavaFileObjects.forSourceString("com/test/MissingGenericTestActivity", Joiner.on("\n").join( 101 | "package com.test;", 102 | "", 103 | "import com.soundcloud.lightcycle.ActivityLightCycle;", 104 | "import com.soundcloud.lightcycle.DefaultActivityLightCycle;", 105 | "import com.soundcloud.lightcycle.LightCycle;", 106 | "import com.soundcloud.lightcycle.LightCycleDispatcher;", 107 | "import com.soundcloud.lightcycle.LightCycleActionBarActivity;", 108 | "", 109 | "import android.app.Activity;", 110 | "", 111 | "public class MissingGenericTestActivity extends LightCycleActionBarActivity {", 112 | "", 113 | " @LightCycle LightCycle1 lightCycle1;", 114 | " @LightCycle LightCycle2 lightCycle2;", 115 | "", 116 | " @Override", 117 | " public void bind(ActivityLightCycle lightCycle) {", 118 | " // nop", 119 | " }", 120 | "", 121 | "}", 122 | "", 123 | "class LightCycle1 extends DefaultActivityLightCycle {", 124 | "}", 125 | "", 126 | "class LightCycle2 extends DefaultActivityLightCycle {", 127 | "}")); 128 | 129 | Truth.assertAbout(JavaSourceSubjectFactory.javaSource()) 130 | .that(missingGenericTestActivity) 131 | .processedWith(new LightCycleProcessor()) 132 | .failsToCompile() 133 | .withErrorContaining("Expected 1 type argument but found 0. TypeArguments:[]. " 134 | + "Did you forget to add generic types?"); 135 | } 136 | 137 | @Test 138 | public void dispatcherNotFound() { 139 | JavaFileObject dispatcherNotFoundTestActivity = JavaFileObjects.forSourceString("com/test/DispatcherNotFoundTestActivity", Joiner.on("\n").join( 140 | "package com.test;", 141 | "", 142 | "import com.soundcloud.lightcycle.ActivityLightCycle;", 143 | "import com.soundcloud.lightcycle.DefaultActivityLightCycle;", 144 | "import com.soundcloud.lightcycle.LightCycle;", 145 | "import com.soundcloud.lightcycle.LightCycleDispatcher;", 146 | "", 147 | "import android.app.Activity;", 148 | "", 149 | "public class DispatcherNotFoundTestActivity extends Activity {", 150 | "", 151 | " @LightCycle LightCycle1 lightCycle1;", 152 | " @LightCycle LightCycle2 lightCycle2;", 153 | "", 154 | "}", 155 | "", 156 | "class LightCycle1 extends DefaultActivityLightCycle {", 157 | "}", 158 | "", 159 | "class LightCycle2 extends DefaultActivityLightCycle {", 160 | "}")); 161 | 162 | Truth.assertAbout(JavaSourceSubjectFactory.javaSource()) 163 | .that(dispatcherNotFoundTestActivity) 164 | .processedWith(new LightCycleProcessor()) 165 | .failsToCompile() 166 | .withErrorContaining("Could not find dispatcher type. " 167 | + "Did you forget to add the generic type?"); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /lightcycle-processor-tests/src/test/java/com/soundcloud/lightcycle/ParameterizedDispatcherTest.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import com.google.common.base.Joiner; 4 | import com.google.common.collect.ImmutableList; 5 | import com.google.common.truth.Truth; 6 | import com.google.testing.compile.JavaFileObjects; 7 | import com.google.testing.compile.JavaSourcesSubjectFactory; 8 | 9 | import org.junit.Test; 10 | 11 | import javax.tools.JavaFileObject; 12 | 13 | public class ParameterizedDispatcherTest { 14 | 15 | @Test 16 | public void shouldGenerateInjectorForParameterizedDispatcher() { 17 | JavaFileObject validTestParameterizedDispatcher = JavaFileObjects.forSourceString("com/test/ValidTestParameterizedDispatcher", Joiner.on("\n").join( 18 | "package com.test;", 19 | "", 20 | "import com.soundcloud.lightcycle.FragmentLightCycle;", 21 | "import com.soundcloud.lightcycle.LightCycle;", 22 | "import com.soundcloud.lightcycle.FragmentLightCycleDispatcher;", 23 | "import com.soundcloud.lightcycle.DefaultFragmentLightCycle;", 24 | "", 25 | "import android.app.Fragment;", 26 | "", 27 | "public class ValidTestParameterizedDispatcher ", 28 | " extends FragmentLightCycleDispatcher{", 29 | "", 30 | " @LightCycle DefaultFragmentLightCycle lightCycle1;", 31 | " @LightCycle DefaultFragmentLightCycle lightCycle2;", 32 | "", 33 | "}")); 34 | 35 | // Because neither the processor or the lib (java libraries) can depend on the api (Android library), 36 | // we have to create a fake `LightCycleActionBarActivity` here for testing purpose. 37 | JavaFileObject fakeFragmentLightCycleDispatcher = JavaFileObjects.forSourceString("com/soundcloud/lightcycle/FragmentLightCycleDispatcher", Joiner.on("\n").join( 38 | "package com.soundcloud.lightcycle;", 39 | "", 40 | "import android.annotation.TargetApi;", 41 | "import android.app.Activity;", 42 | "import android.app.Fragment;", 43 | "import android.content.Context;", 44 | "import android.os.Build;", 45 | "import android.os.Bundle;", 46 | "import android.view.MenuItem;", 47 | "import android.view.View;", 48 | "", 49 | "@TargetApi(Build.VERSION_CODES.HONEYCOMB)", 50 | "public class FragmentLightCycleDispatcher", 51 | " implements LightCycleDispatcher>, FragmentLightCycle {", 52 | "", 53 | " public FragmentLightCycleDispatcher() {", 54 | " }", 55 | "", 56 | " @Override", 57 | " public void bind(FragmentLightCycle lightCycle) { }", 58 | "", 59 | " @Override", 60 | " public void onAttach(T fragment, Activity activity) { }", 61 | "", 62 | " @Override", 63 | " public void onAttach(T fragment, Context context) { }", 64 | "", 65 | " @Override", 66 | " public void onCreate(T fragment, Bundle bundle) { }", 67 | "", 68 | " @Override", 69 | " public void onViewCreated(T fragment, View view, Bundle savedInstanceState) { }", 70 | "", 71 | " @Override", 72 | " public void onActivityCreated(T fragment, Bundle bundle) { }", 73 | "", 74 | " @Override", 75 | " public void onStart(T fragment) { }", 76 | "", 77 | " @Override", 78 | " public void onResume(T fragment) { }", 79 | "", 80 | " @Override", 81 | " public boolean onOptionsItemSelected(T fragment, MenuItem item) {", 82 | " return false;", 83 | " }", 84 | "", 85 | " @Override", 86 | " public void onPause(T fragment) { }", 87 | "", 88 | " @Override", 89 | " public void onStop(T fragment) { }", 90 | "", 91 | " @Override", 92 | " public void onSaveInstanceState(T fragment, Bundle bundle) { }", 93 | "", 94 | " @Override", 95 | " public void onDestroyView(T fragment) { }", 96 | "", 97 | " @Override", 98 | " public void onDestroy(T fragment) { }", 99 | "", 100 | " @Override", 101 | " public void onDetach(T fragment) { }", 102 | "}")); 103 | 104 | JavaFileObject expectedSource = JavaFileObjects.forSourceString("com.test.ValidTestParameterizedDispatcher$LightCycleBinder", Joiner.on("\n").join( 105 | "package com.test;", 106 | "", 107 | "public final class ValidTestParameterizedDispatcher$LightCycleBinder {", 108 | "", 109 | " public static void bind(ValidTestParameterizedDispatcher target) {", 110 | " final com.soundcloud.lightcycle.FragmentLightCycle lightCycle1$Lifted = ", 111 | " com.soundcloud.lightcycle.LightCycles.lift(target.lightCycle1);", 112 | " target.bind(lightCycle1$Lifted);", 113 | " final com.soundcloud.lightcycle.FragmentLightCycle lightCycle2$Lifted = ", 114 | " com.soundcloud.lightcycle.LightCycles.lift(target.lightCycle2);", 115 | " target.bind(lightCycle2$Lifted);", 116 | " }", 117 | "}")); 118 | 119 | Truth.assertAbout(JavaSourcesSubjectFactory.javaSources()) 120 | .that(ImmutableList.of(validTestParameterizedDispatcher, fakeFragmentLightCycleDispatcher)) 121 | .processedWith(new LightCycleProcessor()) 122 | .compilesWithoutError() 123 | .and().generatesSources(expectedSource); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lightcycle-processor/build.gradle: -------------------------------------------------------------------------------- 1 | import org.gradle.internal.jvm.Jvm 2 | 3 | apply plugin: 'checkstyle' 4 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 5 | 6 | checkstyle { 7 | configFile rootProject.file('checkstyle.xml') 8 | showViolations true 9 | } 10 | 11 | dependencies { 12 | implementation project(':lightcycle-api') 13 | implementation deps.javapoet 14 | 15 | testImplementation deps.android 16 | testImplementation deps.compiletesting 17 | 18 | testImplementation files(Jvm.current().toolsJar) 19 | } -------------------------------------------------------------------------------- /lightcycle-processor/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=LightCycle processor 2 | POM_ARTIFACT_ID=lightcycle-processor 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /lightcycle-processor/src/main/java/com/soundcloud/lightcycle/LightCycleBinder.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import com.squareup.javapoet.CodeBlock; 4 | 5 | import javax.annotation.processing.Messager; 6 | import javax.lang.model.element.Element; 7 | import javax.lang.model.type.DeclaredType; 8 | import javax.lang.model.type.TypeMirror; 9 | import javax.tools.Diagnostic; 10 | import java.util.List; 11 | 12 | abstract class LightCycleBinder { 13 | private static final String METHOD_BIND_NAME = "bind"; 14 | private static final String METHOD_BIND_ARGUMENT_NAME = "target"; 15 | private static final String METHOD_LIFT_NAME = LightCycleProcessor.LIB_PACKAGE + ".LightCycles.lift"; 16 | 17 | private static final CodeBlock EMPTY_BLOCK = CodeBlock.builder().build(); 18 | 19 | static final LightCycleBinder DISPATCHER_NOT_FOUND = new LightCycleBinder() { 20 | @Override 21 | CodeBlock generateBind(Messager messager, Element element) { 22 | messager.printMessage(Diagnostic.Kind.ERROR, "Could not find dispatcher type. Did you forget to add the generic type?", element); 23 | return EMPTY_BLOCK; 24 | } 25 | }; 26 | 27 | static final LightCycleBinder EMPTY = new LightCycleBinder() { 28 | @Override 29 | CodeBlock generateBind(Messager messager, Element element) { 30 | return EMPTY_BLOCK; 31 | } 32 | }; 33 | 34 | static LightCycleBinder forFields(final LightCycleDispatcherKind dispatcherKind, final DeclaredType type) { 35 | return new LightCycleBinder() { 36 | @Override 37 | CodeBlock generateBind(Messager messager, Element element) { 38 | final String liftedName = element.getSimpleName() + "$Lifted"; 39 | 40 | final List typeArguments = type.getTypeArguments(); 41 | if (typeArguments.size() != 1) { 42 | error(messager, typeArguments); 43 | return EMPTY_BLOCK; 44 | } else { 45 | return generateLiftAndBind(element, liftedName, typeArguments); 46 | } 47 | } 48 | 49 | private CodeBlock generateLiftAndBind(Element element, String liftedName, List typeArguments) { 50 | final String lightCycleLiftedType = dispatcherKind.toTypeName(typeArguments.get(0).toString()); 51 | return CodeBlock.builder() 52 | .addStatement("final $N $N = $N($N.$N)", 53 | lightCycleLiftedType, 54 | liftedName, 55 | METHOD_LIFT_NAME, 56 | METHOD_BIND_ARGUMENT_NAME, 57 | element.getSimpleName()) 58 | .addStatement("$N.$N($N)", METHOD_BIND_ARGUMENT_NAME, METHOD_BIND_NAME, liftedName) 59 | .build(); 60 | } 61 | 62 | private void error(Messager messager, List typeArguments) { 63 | final String message = String.format("%s: Expected 1 type argument but found %d. TypeArguments:[%s]. Did you forget to add generic types?", 64 | type.toString(), 65 | typeArguments.size(), 66 | typeArguments.toString()); 67 | messager.printMessage(Diagnostic.Kind.ERROR, message, type.asElement()); 68 | } 69 | }; 70 | } 71 | 72 | static LightCycleBinder forParent(final String parentName) { 73 | return new LightCycleBinder() { 74 | @Override 75 | CodeBlock generateBind(Messager messager, Element element) { 76 | return CodeBlock.builder().addStatement("$N.$N($N)", parentName, METHOD_BIND_NAME, METHOD_BIND_ARGUMENT_NAME).build(); 77 | } 78 | }; 79 | } 80 | 81 | abstract CodeBlock generateBind(Messager messager, Element element); 82 | } 83 | -------------------------------------------------------------------------------- /lightcycle-processor/src/main/java/com/soundcloud/lightcycle/LightCycleDispatcherKind.java: -------------------------------------------------------------------------------- 1 | package com.soundcloud.lightcycle; 2 | 3 | import javax.lang.model.element.Name; 4 | 5 | enum LightCycleDispatcherKind { 6 | DEFAULT_ACTIVITIES { 7 | @Override 8 | boolean matches(Name name) { 9 | return name.contentEquals("LightCycleAppCompatActivity") || name.contentEquals("LightCycleActionBarActivity"); 10 | } 11 | 12 | @Override 13 | String toTypeName(String dispatchedType) { 14 | return typeArgumentAsString("ActivityLightCycle", dispatchedType); 15 | } 16 | }, 17 | DEFAULT_FRAGMENT { 18 | @Override 19 | boolean matches(Name name) { 20 | return name.contentEquals("LightCycleFragment"); 21 | } 22 | 23 | @Override 24 | String toTypeName(String dispatchedType) { 25 | return typeArgumentAsString("FragmentLightCycle", dispatchedType); 26 | } 27 | }, 28 | DEFAULT_SUPPORT_FRAGMENT { 29 | @Override 30 | boolean matches(Name name) { 31 | return name.contentEquals("LightCycleSupportFragment"); 32 | } 33 | 34 | @Override 35 | String toTypeName(String dispatchedType) { 36 | return typeArgumentAsString("SupportFragmentLightCycle", dispatchedType); 37 | } 38 | }, 39 | DEFAULT_PREFERENCE_FRAGMENT { 40 | @Override 41 | boolean matches(Name name) { 42 | return name.contentEquals("LightCyclePreferenceFragmentCompat"); 43 | } 44 | 45 | @Override 46 | String toTypeName(String dispatchedType) { 47 | return typeArgumentAsString("SupportFragmentLightCycle", dispatchedType); 48 | } 49 | }, 50 | DEFAULT_SUPPORT_DIALOG_FRAGMENT { 51 | @Override 52 | boolean matches(Name name) { 53 | return name.contentEquals("LightCycleSupportDialogFragment"); 54 | } 55 | 56 | @Override 57 | String toTypeName(String dispatchedType) { 58 | return typeArgumentAsString("SupportFragmentLightCycle", dispatchedType); 59 | } 60 | }, 61 | BASE_ACTIVITY_DISPATCHER { 62 | @Override 63 | boolean matches(Name name) { 64 | return name.contentEquals("ActivityLightCycleDispatcher"); 65 | } 66 | 67 | @Override 68 | String toTypeName(String dispatchedType) { 69 | return typeArgumentAsString("ActivityLightCycle", dispatchedType); 70 | } 71 | }, 72 | BASE_SUPPORT_FRAGMENT_DISPATCHER { 73 | @Override 74 | boolean matches(Name name) { 75 | return name.contentEquals("SupportFragmentLightCycleDispatcher"); 76 | } 77 | 78 | @Override 79 | String toTypeName(String dispatchedType) { 80 | return typeArgumentAsString("SupportFragmentLightCycle", dispatchedType); 81 | } 82 | }, 83 | BASE_FRAGMENT_DISPATCHER { 84 | @Override 85 | boolean matches(Name name) { 86 | return name.contentEquals("FragmentLightCycleDispatcher"); 87 | } 88 | 89 | @Override 90 | String toTypeName(String dispatchedType) { 91 | return typeArgumentAsString("FragmentLightCycle", dispatchedType); 92 | } 93 | }, 94 | DISPATCHER_INTERFACE { 95 | @Override 96 | boolean matches(Name name) { 97 | return name.contentEquals("LightCycleDispatcher"); 98 | } 99 | 100 | @Override 101 | String toTypeName(String dispatchedType) { 102 | return dispatchedType; 103 | } 104 | 105 | }; 106 | 107 | private static String typeArgumentAsString(String lightCycleType, String componentType) { 108 | return String.format("%s.%s<%s>", LightCycleProcessor.LIB_PACKAGE, lightCycleType, componentType); 109 | } 110 | 111 | abstract boolean matches(Name name); 112 | 113 | abstract String toTypeName(String dispatchedType); 114 | } 115 | -------------------------------------------------------------------------------- /lightcycle-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors: -------------------------------------------------------------------------------- 1 | com.soundcloud.lightcycle.LightCycleProcessor,aggregating 2 | -------------------------------------------------------------------------------- /lightcycle-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | com.soundcloud.lightcycle.LightCycleProcessor 2 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | 2 | set -e 3 | set -x 4 | 5 | VERSION=$1 6 | 7 | updateToVersion() { 8 | version=$1 9 | sed -i"" "s/VERSION_NAME=.*/VERSION_NAME=$version/" gradle.properties 10 | git add gradle.properties 11 | git diff --quiet --exit-code --cached || git commit -m "Bump version to $version" 12 | } 13 | 14 | release() { 15 | updateToVersion "$VERSION" 16 | git tag $VERSION 17 | git push origin "$VERSION" 18 | git push origin master 19 | ./gradlew uploadArchives 20 | } 21 | 22 | git checkout master && git pull 23 | release 24 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':lightcycle-api' 2 | include ':lightcycle-lib' 3 | include ':lightcycle-processor' 4 | include ':lightcycle-processor-tests' 5 | include ':lightcycle-integration-test' 6 | include ':examples:basic' 7 | include ':examples:real-world' 8 | --------------------------------------------------------------------------------