├── domic
├── android
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── lyft
│ │ │ │ └── domic
│ │ │ │ └── android
│ │ │ │ ├── annotations
│ │ │ │ └── MutatedByFramework.kt
│ │ │ │ ├── AndroidButton.kt
│ │ │ │ ├── rendering
│ │ │ │ ├── RenderingAction.kt
│ │ │ │ ├── RenderingBuffer.kt
│ │ │ │ ├── RenderingBufferImpl.kt
│ │ │ │ └── AndroidRenderer.kt
│ │ │ │ ├── AndroidEditText.kt
│ │ │ │ ├── AndroidCompoundButton.kt
│ │ │ │ ├── AndroidTextView.kt
│ │ │ │ └── AndroidView.kt
│ │ └── test
│ │ │ └── java
│ │ │ └── com
│ │ │ └── lyft
│ │ │ └── domic
│ │ │ └── android
│ │ │ └── rendering
│ │ │ ├── RenderingBufferImplTest.kt
│ │ │ ├── TestChoreographer.kt
│ │ │ ├── AbstractRenderingBufferTest.kt
│ │ │ └── AndroidRendererTest.kt
│ └── build.gradle
├── api
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ └── com
│ │ └── lyft
│ │ └── domic
│ │ └── api
│ │ ├── Button.kt
│ │ ├── extensions.kt
│ │ ├── rendering
│ │ ├── Change.kt
│ │ └── Renderer.kt
│ │ ├── EditText.kt
│ │ ├── CompoundButton.kt
│ │ ├── TextView.kt
│ │ └── View.kt
├── util
│ ├── build.gradle
│ └── src
│ │ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── lyft
│ │ │ └── domic
│ │ │ └── util
│ │ │ └── SharedDistinctUntilChangedTest.kt
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── lyft
│ │ └── domic
│ │ └── util
│ │ └── SharedDistinctUntilChanged.kt
└── test
│ ├── build.gradle
│ └── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── lyft
│ │ └── domic
│ │ └── test
│ │ ├── TestButton.kt
│ │ ├── TestPublishRelay.kt
│ │ ├── TestCompoundButton.kt
│ │ ├── TestEditText.kt
│ │ ├── TestTextView.kt
│ │ └── TestView.kt
│ └── test
│ └── java
│ └── com
│ └── lyft
│ └── domic
│ └── test
│ ├── TestPublishRelayTest.kt
│ └── TestViewTest.kt
├── samples
├── shared
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ └── layout
│ │ │ │ └── activity_main.xml
│ │ │ └── java
│ │ │ └── com
│ │ │ └── lyft
│ │ │ └── domic
│ │ │ └── samples
│ │ │ └── shared
│ │ │ └── signin
│ │ │ ├── SignInService.kt
│ │ │ ├── RealSignInService.kt
│ │ │ └── TestSignInService.kt
│ └── build.gradle
├── performance
│ ├── src
│ │ └── main
│ │ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ └── layout
│ │ │ │ ├── activity_main.xml
│ │ │ │ └── activity_performance.xml
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── lyft
│ │ │ │ └── domic
│ │ │ │ └── samples
│ │ │ │ └── performance
│ │ │ │ ├── main
│ │ │ │ ├── MainView.kt
│ │ │ │ ├── MainViewModel.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── AndroidMainView.kt
│ │ │ │ ├── domic
│ │ │ │ ├── DomicView.kt
│ │ │ │ ├── DomicActivity.kt
│ │ │ │ ├── AndroidDomicView.kt
│ │ │ │ └── DomicViewModel.kt
│ │ │ │ ├── regular
│ │ │ │ ├── RegularView.kt
│ │ │ │ ├── RegularActivity.kt
│ │ │ │ ├── AndroidRegularView.kt
│ │ │ │ └── RegularPresenter.kt
│ │ │ │ └── PerformanceApplication.kt
│ │ │ └── AndroidManifest.xml
│ ├── proguard-rules.pro
│ └── build.gradle
├── mvp
│ ├── proguard-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── lyft
│ │ │ │ │ └── domic
│ │ │ │ │ └── samples
│ │ │ │ │ └── mvp
│ │ │ │ │ ├── signin
│ │ │ │ │ ├── SignInView.kt
│ │ │ │ │ ├── AndroidSignInView.kt
│ │ │ │ │ └── SignInPresenter.kt
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ └── test
│ │ │ └── java
│ │ │ └── com
│ │ │ └── lyft
│ │ │ └── domic
│ │ │ └── samples
│ │ │ └── mvp
│ │ │ └── signin
│ │ │ └── SignInPresenterTest.kt
│ └── build.gradle
├── mvvm
│ ├── proguard-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── lyft
│ │ │ │ │ └── domic
│ │ │ │ │ └── samples
│ │ │ │ │ └── mvvm
│ │ │ │ │ ├── signin
│ │ │ │ │ ├── SignInView.kt
│ │ │ │ │ ├── AndroidSignInView.kt
│ │ │ │ │ └── SignInViewModel.kt
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ └── test
│ │ │ └── java
│ │ │ └── com
│ │ │ └── lyft
│ │ │ └── domic
│ │ │ └── samples
│ │ │ └── mvvm
│ │ │ └── signin
│ │ │ └── SignInViewModelTest.kt
│ └── build.gradle
└── redux
│ └── rxredux
│ ├── proguard-rules.pro
│ ├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── lyft
│ │ │ │ └── domic
│ │ │ │ └── samples
│ │ │ │ └── redux
│ │ │ │ └── rxredux
│ │ │ │ ├── signin
│ │ │ │ ├── SignInView.kt
│ │ │ │ ├── AndroidSignInView.kt
│ │ │ │ └── SignInStateMachine.kt
│ │ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
│ └── test
│ │ └── java
│ │ └── com
│ │ └── lyft
│ │ └── samples
│ │ └── redux
│ │ └── rxredux
│ │ └── signin
│ │ └── SignInStateMachineTest.kt
│ └── build.gradle
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── lint.gradle
└── dependency-resolution.gradle
├── CODE_OF_CONDUCT.md
├── ci
└── build.sh
├── settings.gradle
├── .gitignore
├── .travis.yml
├── gradle.properties
├── gradlew.bat
├── dependencies.gradle
├── gradlew
├── README.md
└── LICENSE
/domic/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/samples/shared/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/samples/shared/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/shared/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/shared/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/shared/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/shared/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/shared/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/shared/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/shared/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/shared/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/shared/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/performance/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/performance/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/performance/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/performance/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/performance/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/performance/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/shared/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/shared/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/shared/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/shared/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/performance/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/performance/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/performance/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/performance/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/shared/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/shared/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/shared/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/shared/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/domic/api/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java-library'
2 | apply plugin: 'kotlin'
3 |
4 | dependencies {
5 | api libraries.kotlinStdlib
6 | api libraries.rxJava
7 | }
8 |
--------------------------------------------------------------------------------
/samples/mvp/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # For more details, see
3 | # http://developer.android.com/guide/developing/tools/proguard.html
4 |
--------------------------------------------------------------------------------
/samples/performance/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/performance/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/performance/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/performance/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/shared/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/shared/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/mvvm/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # For more details, see
3 | # http://developer.android.com/guide/developing/tools/proguard.html
4 |
--------------------------------------------------------------------------------
/samples/performance/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/performance/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/performance/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/performance/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | This project is governed by [Lyft's code of conduct](https://github.com/lyft/code-of-conduct).
2 | All contributors and participants agree to abide by its terms.
3 |
--------------------------------------------------------------------------------
/samples/performance/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # For more details, see
3 | # http://developer.android.com/guide/developing/tools/proguard.html
4 |
--------------------------------------------------------------------------------
/samples/performance/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyft/domic/HEAD/samples/performance/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/redux/rxredux/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # For more details, see
3 | # http://developer.android.com/guide/developing/tools/proguard.html
4 |
--------------------------------------------------------------------------------
/samples/performance/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Domic Performance
3 | Regular UI
4 | Domic
5 |
6 |
--------------------------------------------------------------------------------
/ci/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -eu
3 |
4 | # You can run it from any directory.
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | PROJECT_DIR="$DIR/.."
7 |
8 | pushd "$PROJECT_DIR" > /dev/null
9 |
10 | ./gradlew build --stacktrace
11 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/domic/util/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java-library'
2 | apply plugin: 'kotlin'
3 |
4 | dependencies {
5 | api libraries.kotlinStdlib
6 | api libraries.rxJava
7 | }
8 |
9 | dependencies {
10 | testImplementation testLibraries.junit
11 | }
12 |
--------------------------------------------------------------------------------
/samples/shared/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/samples/shared/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Virtual DOM Sample
3 | Email
4 | Password
5 | Sign in
6 |
7 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':domic:api'
2 | include ':domic:android'
3 | include ':domic:test'
4 | include ':domic:util'
5 |
6 | include ':samples:mvvm'
7 | include ':samples:redux:rxredux'
8 | include ':samples:mvp'
9 | include ':samples:performance'
10 | include ':samples:shared'
11 |
--------------------------------------------------------------------------------
/domic/android/src/test/java/com/lyft/domic/android/rendering/RenderingBufferImplTest.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.android.rendering
2 |
3 | class RenderingBufferImplTest : AbstractRenderingBufferTest() {
4 | override fun createRenderingBuffer(): RenderingBuffer = RenderingBufferImpl()
5 | }
--------------------------------------------------------------------------------
/domic/api/src/main/kotlin/com/lyft/domic/api/Button.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.api
2 |
3 | interface Button : TextView {
4 |
5 | override val observe: Observe
6 | override val change: Change
7 |
8 | interface Observe : TextView.Observe
9 |
10 | interface Change : TextView.Change
11 | }
12 |
--------------------------------------------------------------------------------
/domic/api/src/main/kotlin/com/lyft/domic/api/extensions.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.api
2 |
3 | import com.lyft.domic.api.rendering.Change
4 | import io.reactivex.Observable
5 | import io.reactivex.disposables.Disposable
6 |
7 | inline fun Observable.subscribe(crossinline bindFunc: (stream: Observable) -> Disposable): Disposable =
8 | bindFunc.invoke(this)
9 |
10 |
--------------------------------------------------------------------------------
/domic/test/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java-library'
2 | apply plugin: 'kotlin'
3 |
4 | dependencies {
5 | api project(':domic:api')
6 | }
7 |
8 | dependencies {
9 | implementation project(':domic:util')
10 | implementation libraries.rxRelay
11 | }
12 |
13 | dependencies {
14 | testImplementation testLibraries.junit
15 | testImplementation testLibraries.assertJ
16 | }
17 |
--------------------------------------------------------------------------------
/samples/mvvm/src/main/java/com/lyft/domic/samples/mvvm/signin/SignInView.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.mvvm.signin
2 |
3 | import com.lyft.domic.api.Button
4 | import com.lyft.domic.api.EditText
5 | import com.lyft.domic.api.TextView
6 |
7 | interface SignInView {
8 | val emailEditText: EditText
9 | val passwordEditText: EditText
10 | val signInButton: Button
11 | val resultTextView: TextView
12 | }
13 |
--------------------------------------------------------------------------------
/domic/api/src/main/kotlin/com/lyft/domic/api/rendering/Change.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.api.rendering
2 |
3 | /**
4 | * Change to be rendered.
5 | *
6 | * [Renderer] might use [equals] and [hashCode] to compare [Change]s and optimize rendering pipeline based on that.
7 | */
8 | interface Change {
9 |
10 | /**
11 | * Called once by [Renderer] on appropriate thread (by default on Main Thread).
12 | */
13 | fun perform()
14 | }
15 |
--------------------------------------------------------------------------------
/samples/performance/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 | .gradle/
5 | build/
6 |
7 | # files for the dex VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # generated files
14 | bin/
15 | gen/
16 | out/
17 | target/
18 | /captures
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Eclipse project files
24 | .classpath
25 | .project
26 |
27 | # Idea IDE files
28 | *.iml
29 | .idea/
30 |
31 | *.DS_STORE
32 |
--------------------------------------------------------------------------------
/samples/shared/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/domic/api/src/main/kotlin/com/lyft/domic/api/EditText.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.api
2 |
3 | import io.reactivex.Observable
4 | import io.reactivex.disposables.Disposable
5 |
6 | interface EditText :TextView {
7 |
8 | override val observe: Observe
9 | override val change: Change
10 |
11 | interface Observe : TextView.Observe
12 |
13 | interface Change : TextView.Change {
14 |
15 | fun selection(selectionValues: Observable): Disposable
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/samples/performance/src/main/java/com/lyft/domic/samples/performance/main/MainView.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.performance.main
2 |
3 | import android.support.annotation.AnyThread
4 | import com.lyft.domic.api.Button
5 | import io.reactivex.Completable
6 |
7 | interface MainView {
8 | val regularUiButton: Button
9 | val domicButton: Button
10 |
11 | @AnyThread
12 | fun navigateToRegular(): Completable
13 |
14 | @AnyThread
15 | fun navigateToDomic(): Completable
16 | }
17 |
--------------------------------------------------------------------------------
/domic/android/src/main/java/com/lyft/domic/android/annotations/MutatedByFramework.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.android.annotations
2 |
3 | import kotlin.annotation.AnnotationRetention.SOURCE
4 | import kotlin.annotation.AnnotationTarget.PROPERTY
5 |
6 | /**
7 | * Means that property needs to be observed in order to keep state in sync between Android Framework and Domic.
8 | * Also means that there is no point in making property lazy.
9 | */
10 | @Target(PROPERTY)
11 | @Retention(SOURCE)
12 | internal annotation class MutatedByFramework
--------------------------------------------------------------------------------
/samples/redux/rxredux/src/main/java/com/lyft/domic/samples/redux/rxredux/signin/SignInView.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.redux.rxredux.signin
2 |
3 | import com.lyft.domic.samples.redux.rxredux.signin.SignInStateMachine.Action
4 | import com.lyft.domic.samples.redux.rxredux.signin.SignInStateMachine.State
5 | import io.reactivex.Observable
6 | import io.reactivex.disposables.Disposable
7 |
8 | interface SignInView {
9 | val actions: Observable
10 | fun render(signInState: Observable): Disposable
11 | }
12 |
--------------------------------------------------------------------------------
/samples/shared/src/main/java/com/lyft/domic/samples/shared/signin/SignInService.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.shared.signin
2 |
3 | import io.reactivex.Observable
4 |
5 | interface SignInService {
6 |
7 | data class Credentials(val email: CharSequence, val password: CharSequence)
8 |
9 | sealed class SignInResult {
10 | object Success : SignInResult()
11 | data class Error(val cause: Throwable) : SignInResult()
12 | }
13 |
14 | fun signIn(credentials: Credentials): Observable
15 | }
16 |
--------------------------------------------------------------------------------
/domic/api/src/main/kotlin/com/lyft/domic/api/CompoundButton.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.api
2 |
3 | import io.reactivex.Observable
4 | import io.reactivex.disposables.Disposable
5 |
6 | interface CompoundButton : Button {
7 |
8 | override val observe: Observe
9 | override val change: Change
10 |
11 | interface Observe : Button.Observe {
12 | val checked: Observable
13 | }
14 |
15 | interface Change : Button.Change {
16 | fun checked(checkedValues: Observable): Disposable
17 | }
18 | }
--------------------------------------------------------------------------------
/samples/mvp/src/main/java/com/lyft/domic/samples/mvp/signin/SignInView.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.mvp.signin
2 |
3 | import io.reactivex.Observable
4 | import io.reactivex.disposables.Disposable
5 |
6 | interface SignInView {
7 | fun observeEmail(): Observable
8 | fun observePassword(): Observable
9 | fun observeSingInSubmitActions(): Observable
10 |
11 | fun changeSignInEnable(enabledValues: Observable): Disposable
12 | fun changeResultText(textValues: Observable): Disposable
13 | }
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 |
3 | jdk:
4 | - oraclejdk8
5 |
6 | before_install:
7 | - mkdir "$ANDROID_HOME/licenses"
8 | - echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" > "$ANDROID_HOME/licenses/android-sdk-license"
9 |
10 | # Prevent Travis from running automatic project install.
11 | install:
12 | - true
13 |
14 | script:
15 | - ci/build.sh
16 |
17 | before_cache:
18 | - rm -f "$HOME"/.gradle/caches/modules-2/modules-2.lock
19 |
20 | cache:
21 | directories:
22 | - $HOME/.gradle/caches
23 | - $HOME/.gradle/wrapper
24 |
25 | notifications:
26 | email: false
27 |
--------------------------------------------------------------------------------
/samples/shared/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | defaultConfig {
6 | minSdkVersion versions.minSdk
7 | compileSdkVersion versions.compileSdk
8 | buildToolsVersion versions.buildTools
9 | }
10 |
11 | lintOptions {
12 | disable 'UnusedResources' // Lint doesn't see resource usage from other modules.
13 | }
14 | }
15 |
16 | dependencies {
17 | api libraries.kotlinStdlib
18 | api libraries.rxJava
19 | api libraries.rxRelay
20 | api supportLibraries.appCompat
21 | api supportLibraries.constraintLayout
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/domic/api/src/main/kotlin/com/lyft/domic/api/TextView.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.api
2 |
3 | import io.reactivex.Observable
4 | import io.reactivex.disposables.Disposable
5 |
6 | interface TextView : View {
7 |
8 | override val observe: Observe
9 | override val change: Change
10 |
11 | interface Observe : View.Observe {
12 | val textChanges: Observable
13 |
14 | // TODO: delete, suggest to map [textChanges] to [Unit].
15 | val textChangeEvents: Observable
16 | }
17 |
18 | interface Change : View.Change {
19 |
20 | fun text(textValues: Observable): Disposable
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/domic/android/src/main/java/com/lyft/domic/android/AndroidButton.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.android
2 |
3 | import com.lyft.domic.api.Button
4 | import com.lyft.domic.api.TextView
5 | import com.lyft.domic.api.rendering.Renderer
6 |
7 | class AndroidButton(
8 | private val realButton: android.widget.Button,
9 | private val renderer: Renderer
10 | ) : Button {
11 |
12 | private val asTextView: TextView = AndroidTextView(realButton, renderer)
13 |
14 | override val observe: Button.Observe = object : Button.Observe, TextView.Observe by asTextView.observe {
15 |
16 | }
17 |
18 | override val change: Button.Change = object : Button.Change, TextView.Change by asTextView.change {
19 |
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/samples/performance/src/main/java/com/lyft/domic/samples/performance/domic/DomicView.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.performance.domic
2 |
3 | import com.lyft.domic.api.Button
4 | import com.lyft.domic.api.CompoundButton
5 | import com.lyft.domic.api.EditText
6 | import com.lyft.domic.api.TextView
7 |
8 | interface DomicView {
9 | val counter0: TextView
10 | val counter1: TextView
11 | val counter2: TextView
12 | val counter3: TextView
13 | val counter4: TextView
14 | val counter5: TextView
15 | val counter6: TextView
16 | val counter7: TextView
17 | val counter8: TextView
18 | val counter9: TextView
19 |
20 | val button: Button
21 | val checkBox: CompoundButton
22 | val radioButton: CompoundButton
23 | }
24 |
--------------------------------------------------------------------------------
/samples/performance/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/samples/performance/src/main/java/com/lyft/domic/samples/performance/regular/RegularView.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.performance.regular
2 |
3 | interface RegularView {
4 | fun setCounter0Text(text: CharSequence)
5 | fun setCounter1Text(text: CharSequence)
6 | fun setCounter2Text(text: CharSequence)
7 | fun setCounter3Text(text: CharSequence)
8 | fun setCounter4Text(text: CharSequence)
9 | fun setCounter5Text(text: CharSequence)
10 | fun setCounter6Text(text: CharSequence)
11 | fun setCounter7Text(text: CharSequence)
12 | fun setCounter8Text(text: CharSequence)
13 | fun setCounter9Text(text: CharSequence)
14 |
15 | fun setButtonEnabled(enabled: Boolean)
16 | fun setCheckBoxChecked(checked: Boolean)
17 | fun setRadioButtonChecked(checked: Boolean)
18 | }
19 |
--------------------------------------------------------------------------------
/samples/shared/src/main/java/com/lyft/domic/samples/shared/signin/RealSignInService.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.shared.signin
2 |
3 | import com.lyft.domic.samples.shared.signin.SignInService.SignInResult
4 | import io.reactivex.Observable
5 | import java.util.*
6 | import java.util.concurrent.TimeUnit.SECONDS
7 |
8 | class RealSignInService : SignInService {
9 | override fun signIn(credentials: SignInService.Credentials) = Observable
10 | .fromCallable { Random().nextBoolean() }
11 | .delay { Observable.timer(Random().nextInt(5).toLong(), SECONDS) }
12 | .map {
13 | when (it) {
14 | true -> SignInResult.Success
15 | false -> SignInResult.Error(cause = Exception("some network problem"))
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/domic/test/src/main/java/com/lyft/domic/test/TestButton.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.test
2 |
3 | import com.lyft.domic.api.Button
4 | import com.lyft.domic.api.TextView
5 |
6 | class TestButton : Button {
7 |
8 | interface Check : TestTextView.Check
9 |
10 | interface Simulate : TestTextView.Simulate
11 |
12 | private val asTestTextView = TestTextView()
13 |
14 | override val observe: Button.Observe = object : Button.Observe, TextView.Observe by asTestTextView.observe {
15 |
16 | }
17 |
18 | override val change: Button.Change = object : Button.Change, TextView.Change by asTestTextView.change {
19 |
20 | }
21 |
22 | val check: Check = object : Check, TestTextView.Check by asTestTextView.check {
23 |
24 | }
25 |
26 | val simulate: Simulate = object : Simulate, TestTextView.Simulate by asTestTextView.simulate {
27 |
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/samples/shared/src/main/java/com/lyft/domic/samples/shared/signin/TestSignInService.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.shared.signin
2 |
3 | import com.jakewharton.rxrelay2.PublishRelay
4 | import io.reactivex.Observable
5 | import io.reactivex.observers.TestObserver
6 |
7 | class TestSignInService : SignInService {
8 |
9 | val signInCredentialsRelay = PublishRelay.create()
10 | val signInCredentialsObserver = TestObserver()
11 | val signIn = PublishRelay.create()
12 |
13 | init {
14 | signInCredentialsRelay.subscribe(signInCredentialsObserver)
15 | }
16 |
17 | override fun signIn(credentials: SignInService.Credentials): Observable {
18 | signInCredentialsRelay.accept(credentials)
19 | return signIn
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/samples/mvvm/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/domic/android/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | defaultConfig {
6 | minSdkVersion versions.minSdk
7 | compileSdkVersion versions.compileSdk
8 |
9 | buildToolsVersion versions.buildTools
10 | }
11 |
12 | compileOptions {
13 | sourceCompatibility JavaVersion.VERSION_1_8
14 | targetCompatibility JavaVersion.VERSION_1_8
15 | }
16 | }
17 |
18 | dependencies {
19 | api project(':domic:api')
20 | }
21 |
22 | dependencies {
23 | implementation project(':domic:util')
24 | implementation libraries.rxAndroid
25 | implementation libraries.rxBinding
26 | implementation libraries.rxReplayingShareKotlin
27 | }
28 |
29 | dependencies {
30 | testImplementation testLibraries.junit
31 | testImplementation testLibraries.assertJ
32 | testImplementation testLibraries.kotlinMockito
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/samples/redux/rxredux/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/samples/mvp/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/samples/mvvm/src/main/java/com/lyft/domic/samples/mvvm/signin/AndroidSignInView.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.mvvm.signin
2 |
3 | import android.view.ViewGroup
4 | import com.lyft.domic.android.AndroidButton
5 | import com.lyft.domic.android.AndroidEditText
6 | import com.lyft.domic.android.AndroidTextView
7 | import com.lyft.domic.api.rendering.Renderer
8 | import com.lyft.domic.samples.mvvm.R
9 |
10 | class AndroidSignInView(root: ViewGroup, renderer: Renderer) : SignInView {
11 | override val emailEditText by lazy { AndroidEditText(root.findViewById(R.id.email_edit_text), renderer) }
12 | override val passwordEditText by lazy { AndroidEditText(root.findViewById(R.id.password_edit_text), renderer) }
13 | override val signInButton by lazy { AndroidButton(root.findViewById(R.id.sign_in_button), renderer) }
14 | override val resultTextView by lazy { AndroidTextView(root.findViewById(R.id.sign_result_text_view), renderer) }
15 | }
16 |
--------------------------------------------------------------------------------
/gradle/lint.gradle:
--------------------------------------------------------------------------------
1 | def domicLintOptions() {
2 | return {
3 | warningsAsErrors true
4 | abortOnError true
5 |
6 | textReport true
7 | textOutput 'stdout'
8 |
9 | htmlReport true
10 | xmlReport true
11 |
12 | enable 'Interoperability'
13 | }
14 | }
15 |
16 | subprojects { subProject ->
17 | subProject.afterEvaluate {
18 | def plugins = subProject.getPlugins()
19 |
20 | def isAndroid = plugins.hasPlugin('com.android.application') || plugins.hasPlugin('com.android.library')
21 | def isJvm = plugins.hasPlugin('java-library') || plugins.hasPlugin('kotlin')
22 |
23 | if (isAndroid) {
24 | configure(android.lintOptions, domicLintOptions())
25 | } else if (isJvm) {
26 | subProject.plugins.apply('com.android.lint') // Lint supports JVM modules too.
27 | configure(lintOptions, domicLintOptions())
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/samples/performance/src/main/java/com/lyft/domic/samples/performance/main/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.performance.main
2 |
3 | import io.reactivex.disposables.CompositeDisposable
4 | import io.reactivex.disposables.Disposable
5 | import io.reactivex.rxkotlin.plusAssign
6 |
7 | class MainViewModel(view: MainView) : Disposable {
8 |
9 | private val disposable = CompositeDisposable()
10 |
11 | init {
12 | disposable += view.regularUiButton
13 | .observe
14 | .clicks
15 | .switchMapCompletable { view.navigateToRegular() }
16 | .subscribe()
17 |
18 | disposable += view.domicButton
19 | .observe
20 | .clicks
21 | .switchMapCompletable { view.navigateToDomic() }
22 | .subscribe()
23 | }
24 |
25 | override fun isDisposed() = disposable.isDisposed
26 |
27 | override fun dispose() = disposable.dispose()
28 | }
29 |
--------------------------------------------------------------------------------
/gradle/dependency-resolution.gradle:
--------------------------------------------------------------------------------
1 | subprojects { subProject ->
2 | afterEvaluate {
3 | def plugins = subProject.getPlugins()
4 |
5 | def isAndroid = plugins.hasPlugin('com.android.application') || plugins.hasPlugin('com.android.library')
6 | def isJvm = plugins.hasPlugin('java-library') || plugins.hasPlugin('kotlin')
7 |
8 | if (isAndroid || isJvm) {
9 | subProject.configurations.all { configuration ->
10 | // Ignore ':lintClassPath' configuration as it has version conflicts.
11 | if (!"$configuration".contains(':lintClassPath')) {
12 | resolutionStrategy {
13 | failOnVersionConflict()
14 |
15 | def allLibraries = libraries.values() +
16 | supportLibraries.values() +
17 | testLibraries.values()
18 |
19 | allLibraries.each { library -> force library }
20 | }
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/domic/api/src/main/kotlin/com/lyft/domic/api/View.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.api
2 |
3 | import io.reactivex.Observable
4 | import io.reactivex.disposables.Disposable
5 |
6 | interface View {
7 |
8 | enum class Visibility {
9 | GONE,
10 | INVISIBLE,
11 | VISIBLE,
12 | }
13 |
14 | val observe: Observe
15 | val change: Change
16 |
17 | interface Observe {
18 | val clicks: Observable
19 | val focus: Observable
20 | val longClicks: Observable
21 | }
22 |
23 | interface Change {
24 |
25 | fun activated(activatedValues: Observable): Disposable
26 |
27 | fun alpha(alphaValues: Observable): Disposable
28 |
29 | fun enabled(enabledValues: Observable): Disposable
30 |
31 | fun focusable(focusableValues: Observable): Disposable
32 |
33 | fun focusableInTouchMode(focusableInTouchModeValues: Observable): Disposable
34 |
35 | fun visibility(visibilityValues: Observable): Disposable
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/domic/android/src/main/java/com/lyft/domic/android/rendering/RenderingAction.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.android.rendering
2 |
3 | import com.lyft.domic.api.rendering.Change
4 | import io.reactivex.Observable
5 |
6 | /**
7 | * Abstracts object property change and implements equals in a way that matches pro
8 | */
9 | internal abstract class AndroidChange(private val obj: Any, private val propertyId: Int) : Change {
10 |
11 | override fun equals(other: Any?) = other is AndroidChange
12 | &&
13 | other.obj === this.obj
14 | &&
15 | other.propertyId == this.propertyId
16 |
17 | override fun hashCode() = obj.hashCode() + propertyId
18 |
19 | override fun toString() = "AndroidChange(obj = ${obj.javaClass}(${obj.hashCode()}), propertyId = $propertyId)"
20 | }
21 |
22 | internal fun Observable.mapToChange(obj: Any, propertyId: Int, func: (T) -> Unit): Observable = map { value ->
23 | object : AndroidChange(obj, propertyId) {
24 | override fun perform() {
25 | func.invoke(value)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/domic/test/src/main/java/com/lyft/domic/test/TestPublishRelay.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.test
2 |
3 | import com.jakewharton.rxrelay2.PublishRelay
4 | import com.jakewharton.rxrelay2.Relay
5 | import io.reactivex.Observer
6 | import java.util.concurrent.atomic.AtomicReference
7 |
8 | internal class TestPublishRelay private constructor() : Relay() {
9 |
10 | companion object {
11 | fun create(): TestPublishRelay = TestPublishRelay()
12 | }
13 |
14 | private val actualRelay = PublishRelay.create()
15 |
16 | private val lastValue = AtomicReference()
17 |
18 | fun lastValue(): T? = lastValue.get()
19 |
20 | override fun accept(value: T) {
21 | val previousValue = lastValue.get()
22 |
23 | if (value != previousValue && lastValue.compareAndSet(previousValue, value)) {
24 | actualRelay.accept(value)
25 | }
26 | }
27 |
28 | override fun hasObservers(): Boolean = actualRelay.hasObservers()
29 |
30 | override fun subscribeActual(observer: Observer) {
31 | actualRelay.subscribeActual(observer)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/samples/performance/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/samples/performance/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | buildToolsVersion versions.buildTools
6 |
7 | defaultConfig {
8 | applicationId 'com.lyf.domic.samples.performance'
9 | compileSdkVersion versions.compileSdk
10 | targetSdkVersion versions.targetSdk
11 | minSdkVersion versions.minSdk
12 | versionCode 1
13 | versionName '0.0.1'
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled true
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 |
23 | lintOptions {
24 | disable 'GoogleAppIndexingWarning'
25 | }
26 | }
27 |
28 | // See `dependencies.gradle`.
29 | dependencies {
30 | implementation project(':domic:api')
31 | implementation project(':domic:android')
32 |
33 | implementation libraries.kotlinStdlib
34 |
35 | implementation supportLibraries.appCompat
36 | implementation supportLibraries.constraintLayout
37 |
38 | implementation libraries.rxJava
39 | implementation libraries.rxAndroid
40 | implementation libraries.rxKotlin
41 | }
42 |
--------------------------------------------------------------------------------
/samples/performance/src/main/java/com/lyft/domic/samples/performance/regular/RegularActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.performance.regular
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import com.lyft.domic.samples.performance.PerformanceApplication
6 | import com.lyft.domic.samples.performance.R
7 | import io.reactivex.Single
8 | import io.reactivex.disposables.CompositeDisposable
9 | import io.reactivex.rxkotlin.plusAssign
10 | import io.reactivex.schedulers.Schedulers
11 |
12 | class RegularActivity : AppCompatActivity() {
13 |
14 | private val disposable = CompositeDisposable()
15 |
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 | setContentView(R.layout.activity_performance)
19 |
20 | val view = AndroidRegularView(findViewById(android.R.id.content))
21 |
22 | disposable += Single
23 | .fromCallable { RegularPresenter(view) }
24 | .subscribeOn(Schedulers.computation())
25 | .subscribe { presenter -> disposable += presenter }
26 | }
27 |
28 | override fun onDestroy() {
29 | disposable.dispose()
30 | super.onDestroy()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/samples/performance/src/main/java/com/lyft/domic/samples/performance/main/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.performance.main
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import com.lyft.domic.samples.performance.PerformanceApplication
6 | import com.lyft.domic.samples.performance.R
7 | import io.reactivex.Single
8 | import io.reactivex.disposables.CompositeDisposable
9 | import io.reactivex.rxkotlin.plusAssign
10 | import io.reactivex.schedulers.Schedulers
11 |
12 | class MainActivity : AppCompatActivity() {
13 |
14 | private val disposable = CompositeDisposable()
15 |
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 | setContentView(R.layout.activity_main)
19 |
20 | val view = AndroidMainView(findViewById(android.R.id.content), PerformanceApplication.renderer)
21 |
22 | disposable += Single
23 | .fromCallable { MainViewModel(view) }
24 | .subscribeOn(Schedulers.computation())
25 | .subscribe { viewModel -> disposable += viewModel }
26 | }
27 |
28 | override fun onDestroy() {
29 | disposable.dispose()
30 | super.onDestroy()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/samples/performance/src/main/java/com/lyft/domic/samples/performance/domic/DomicActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.performance.domic
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import com.lyft.domic.samples.performance.PerformanceApplication
6 | import com.lyft.domic.samples.performance.R
7 | import io.reactivex.Single
8 | import io.reactivex.disposables.CompositeDisposable
9 | import io.reactivex.rxkotlin.plusAssign
10 | import io.reactivex.schedulers.Schedulers
11 |
12 | class DomicActivity : AppCompatActivity() {
13 |
14 | private val disposable = CompositeDisposable()
15 |
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 | setContentView(R.layout.activity_performance)
19 |
20 | val view = AndroidDomicView(findViewById(android.R.id.content), PerformanceApplication.renderer)
21 |
22 | disposable += Single
23 | .fromCallable { DomicViewModel(view) }
24 | .subscribeOn(Schedulers.computation())
25 | .subscribe { viewModel -> disposable += viewModel }
26 | }
27 |
28 | override fun onDestroy() {
29 | disposable.dispose()
30 | super.onDestroy()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/domic/android/src/test/java/com/lyft/domic/android/rendering/TestChoreographer.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.android.rendering
2 |
3 | import android.view.Choreographer
4 | import com.nhaarman.mockito_kotlin.any
5 | import com.nhaarman.mockito_kotlin.mock
6 | import com.nhaarman.mockito_kotlin.whenever
7 |
8 | class TestChoreographer {
9 |
10 | // Choreographer is a final class, we can't implement it as interface directly.
11 | val choreographer: Choreographer = mock()
12 |
13 | private val currentFrameCallbacks = mutableSetOf()
14 | private var frameTimeNanos = 0L
15 |
16 | init {
17 | whenever(choreographer.postFrameCallback(any())).then { invocation ->
18 | currentFrameCallbacks += invocation.arguments.first() as Choreographer.FrameCallback
19 | Unit
20 | }
21 | }
22 |
23 | fun callbacksCount(): Int = currentFrameCallbacks.size
24 |
25 | /**
26 | * Simulates call from Android Framework, callbacks are disposed to mimic Framework.
27 | */
28 | fun simulateCallbacksCall() {
29 | val callbacksCopy = HashSet(currentFrameCallbacks)
30 | currentFrameCallbacks.clear()
31 | callbacksCopy.forEach { it.doFrame(frameTimeNanos++) }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/samples/performance/src/main/java/com/lyft/domic/samples/performance/main/AndroidMainView.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.performance.main
2 |
3 | import android.content.Intent
4 | import android.view.ViewGroup
5 | import com.lyft.domic.android.AndroidButton
6 | import com.lyft.domic.api.rendering.Renderer
7 | import com.lyft.domic.samples.performance.R
8 | import com.lyft.domic.samples.performance.domic.DomicActivity
9 | import com.lyft.domic.samples.performance.regular.RegularActivity
10 | import io.reactivex.Completable
11 | import io.reactivex.android.schedulers.AndroidSchedulers.mainThread
12 |
13 | class AndroidMainView(private val root: ViewGroup, renderer: Renderer) : MainView {
14 | override val regularUiButton = AndroidButton(root.findViewById(R.id.button_regular_ui), renderer)
15 | override val domicButton = AndroidButton(root.findViewById(R.id.button_domic), renderer)
16 |
17 | override fun navigateToRegular() = Completable
18 | .fromAction { root.context.startActivity(Intent(root.context, RegularActivity::class.java)) }
19 | .subscribeOn(mainThread())
20 |
21 | override fun navigateToDomic() = Completable
22 | .fromAction { root.context.startActivity(Intent(root.context, DomicActivity::class.java)) }
23 | .subscribeOn(mainThread())
24 | }
25 |
--------------------------------------------------------------------------------
/samples/redux/rxredux/src/main/java/com/lyft/domic/samples/redux/rxredux/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.redux.rxredux
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import com.lyft.domic.android.rendering.AndroidRenderer
6 | import com.lyft.domic.samples.redux.rxredux.signin.AndroidSignInView
7 | import com.lyft.domic.samples.redux.rxredux.signin.SignInStateMachine
8 | import com.lyft.domic.samples.shared.signin.RealSignInService
9 | import io.reactivex.disposables.CompositeDisposable
10 | import io.reactivex.rxkotlin.plusAssign
11 | import io.reactivex.schedulers.Schedulers
12 |
13 | class MainActivity : AppCompatActivity() {
14 |
15 | private val disposable = CompositeDisposable()
16 |
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | setContentView(R.layout.activity_main)
20 |
21 | val renderer = AndroidRenderer()
22 | val view = AndroidSignInView(findViewById(android.R.id.content), renderer)
23 | val stateMachine = SignInStateMachine(view.actions, RealSignInService(), Schedulers.computation())
24 |
25 | disposable += view.render(stateMachine.state)
26 | }
27 |
28 | override fun onDestroy() {
29 | disposable.dispose()
30 | super.onDestroy()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/samples/performance/src/main/java/com/lyft/domic/samples/performance/PerformanceApplication.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.performance
2 |
3 | import android.app.Application
4 | import android.os.Process
5 | import com.lyft.domic.android.rendering.AndroidRenderer
6 | import com.lyft.domic.api.rendering.Renderer
7 | import io.reactivex.plugins.RxJavaPlugins
8 | import java.util.concurrent.ThreadFactory
9 | import java.util.concurrent.atomic.AtomicInteger
10 |
11 | class PerformanceApplication : Application() {
12 |
13 | companion object {
14 | val renderer: Renderer = AndroidRenderer()
15 | }
16 |
17 | override fun onCreate() {
18 | super.onCreate()
19 |
20 | val computationScheduler = RxJavaPlugins.createComputationScheduler(RxThreadFactory(Process.THREAD_PRIORITY_BACKGROUND))
21 | RxJavaPlugins.setComputationSchedulerHandler { computationScheduler }
22 | }
23 |
24 | class RxThreadFactory(private val priority: Int) : ThreadFactory {
25 |
26 | private val idGenerator = AtomicInteger()
27 |
28 | override fun newThread(runnable: Runnable) = Thread(Runnable {
29 | Process.setThreadPriority(priority)
30 | runnable.run()
31 | }).apply {
32 | isDaemon = true
33 | name = "${idGenerator.incrementAndGet()}"
34 | }
35 |
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/domic/test/src/test/java/com/lyft/domic/test/TestPublishRelayTest.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.test
2 |
3 | import io.reactivex.observers.TestObserver
4 | import org.assertj.core.api.Assertions.assertThat
5 | import org.junit.Test
6 |
7 | class TestPublishRelayTest {
8 |
9 | private val relay = TestPublishRelay.create()
10 |
11 | @Test
12 | fun hasNoObserversByDefault() {
13 | assertThat(relay.hasObservers()).isFalse()
14 | }
15 |
16 | @Test
17 | fun hasObserver() {
18 | relay.subscribe(TestObserver())
19 | assertThat(relay.hasObservers()).isTrue()
20 | }
21 |
22 | @Test
23 | fun noValuesByDefault() {
24 | val observer = relay.test()
25 | observer.assertNoValues()
26 | }
27 |
28 | @Test
29 | fun notTerminatedByDefault() {
30 | val observer = relay.test()
31 | observer.assertNotTerminated()
32 | }
33 |
34 | @Test
35 | fun acceptEmitsUniqueValue() {
36 | val observer = relay.test()
37 |
38 | relay.accept("a")
39 | observer.assertValue("a")
40 |
41 | relay.accept("b")
42 | observer.assertValues("a", "b")
43 | }
44 |
45 | @Test
46 | fun acceptSkipsEqualValues() {
47 | val observer = relay.test()
48 |
49 | relay.accept("a")
50 | observer.assertValue("a")
51 |
52 | relay.accept("a")
53 | observer.assertValueCount(1)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/samples/mvp/src/main/java/com/lyft/domic/samples/mvp/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.mvp
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import com.lyft.domic.android.rendering.AndroidRenderer
6 | import com.lyft.domic.samples.mvp.signin.AndroidSignInView
7 | import com.lyft.domic.samples.mvp.signin.SignInPresenter
8 | import com.lyft.domic.samples.shared.signin.RealSignInService
9 | import io.reactivex.Single
10 | import io.reactivex.disposables.CompositeDisposable
11 | import io.reactivex.rxkotlin.plusAssign
12 | import io.reactivex.schedulers.Schedulers.computation
13 |
14 | class MainActivity : AppCompatActivity() {
15 |
16 | private val disposable = CompositeDisposable()
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | setContentView(R.layout.activity_main)
21 |
22 | val renderer = AndroidRenderer()
23 | val view = AndroidSignInView(findViewById(android.R.id.content), renderer)
24 |
25 | disposable += Single
26 | .fromCallable { SignInPresenter(view, RealSignInService()) }
27 | // Showcase that you can observe Virtual DOM on non-main thread.
28 | .subscribeOn(computation())
29 | .subscribe { presenter -> disposable += presenter }
30 | }
31 |
32 | override fun onDestroy() {
33 | disposable.dispose()
34 | super.onDestroy()
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/samples/mvvm/src/main/java/com/lyft/domic/samples/mvvm/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.mvvm
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import com.lyft.domic.android.rendering.AndroidRenderer
6 | import com.lyft.domic.samples.shared.signin.RealSignInService
7 | import com.lyft.domic.samples.mvvm.signin.AndroidSignInView
8 | import com.lyft.domic.samples.mvvm.signin.SignInViewModel
9 | import io.reactivex.Single
10 | import io.reactivex.disposables.CompositeDisposable
11 | import io.reactivex.rxkotlin.plusAssign
12 | import io.reactivex.schedulers.Schedulers
13 |
14 | class MainActivity : AppCompatActivity() {
15 |
16 | private val disposable = CompositeDisposable()
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | setContentView(R.layout.activity_main)
21 |
22 | val renderer = AndroidRenderer()
23 | val view = AndroidSignInView(findViewById(android.R.id.content), renderer)
24 |
25 | disposable += Single
26 | .fromCallable { SignInViewModel(view, RealSignInService()) }
27 | // Showcase that you can observe Virtual DOM on non-main thread.
28 | .subscribeOn(Schedulers.computation())
29 | .subscribe { viewModel -> disposable += viewModel }
30 | }
31 |
32 | override fun onDestroy() {
33 | disposable.dispose()
34 | super.onDestroy()
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/samples/mvp/src/main/java/com/lyft/domic/samples/mvp/signin/AndroidSignInView.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.mvp.signin
2 |
3 | import android.view.ViewGroup
4 | import com.lyft.domic.android.AndroidButton
5 | import com.lyft.domic.android.AndroidEditText
6 | import com.lyft.domic.android.AndroidTextView
7 | import com.lyft.domic.api.rendering.Renderer
8 | import com.lyft.domic.api.subscribe
9 | import com.lyft.domic.samples.mvp.R
10 | import io.reactivex.Observable
11 |
12 | class AndroidSignInView(root: ViewGroup, renderer: Renderer) : SignInView {
13 | private val emailEditText by lazy { AndroidEditText(root.findViewById(R.id.email_edit_text), renderer) }
14 | private val passwordEditText by lazy { AndroidEditText(root.findViewById(R.id.password_edit_text), renderer) }
15 | private val signInButton by lazy { AndroidButton(root.findViewById(R.id.sign_in_button), renderer) }
16 | private val resultTextView by lazy { AndroidTextView(root.findViewById(R.id.sign_result_text_view), renderer) }
17 |
18 | override fun observeEmail() = emailEditText.observe.textChanges
19 | override fun observePassword() = passwordEditText.observe.textChanges
20 | override fun observeSingInSubmitActions() = signInButton.observe.clicks
21 |
22 | override fun changeSignInEnable(enabledValues: Observable) = enabledValues.subscribe(signInButton.change::enabled)
23 | override fun changeResultText(textValues: Observable) = textValues.subscribe(resultTextView.change::text)
24 | }
25 |
--------------------------------------------------------------------------------
/domic/api/src/main/kotlin/com/lyft/domic/api/rendering/Renderer.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.api.rendering
2 |
3 | import io.reactivex.Observable
4 | import io.reactivex.disposables.Disposable
5 |
6 | interface Renderer {
7 |
8 | /**
9 | * "Renders" stream of changes.
10 | *
11 | * Implementation can be opinionated on grouping, and/or ordering of execution.
12 | *
13 | * [Renderer] might use [Change.equals] and [Change.hashCode] to compare [Change]s and optimize
14 | * rendering pipeline based on that, for example render latest equal [Change] arrived
15 | * within same buffering window.
16 | *
17 | * Each [Change]'s [Change.perform] will be called once on proper thread (Main Thread by default).
18 | *
19 | * @return [Disposable] that allows to stop observing the [Observable]. [Renderer] should try
20 | * to remove observed but not yet rendered actions from current buffer.
21 | */
22 | fun render(changes: Observable): Disposable
23 |
24 | /**
25 | * "Renders" what is in the buffer at the moment, blocking the caller thread.
26 | *
27 | * Main purpose of this API is to add a way of solving ["First Frame Problem"](https://github.com/lyft/domic/issues/14).
28 | *
29 | * Implementation can be opinionated on threading and throw an exception if called on wrong thread.
30 | */
31 | fun renderCurrentBuffer()
32 |
33 | /**
34 | * Shuts down the [Renderer]. [Renderer] can not be reused after shut down.
35 | */
36 | fun shutdown()
37 | }
38 |
--------------------------------------------------------------------------------
/domic/android/src/main/java/com/lyft/domic/android/AndroidEditText.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.android
2 |
3 | import com.lyft.domic.android.rendering.mapToChange
4 | import com.lyft.domic.api.EditText
5 | import com.lyft.domic.api.TextView
6 | import com.lyft.domic.api.rendering.Renderer
7 | import com.lyft.domic.api.subscribe
8 | import com.lyft.domic.util.sharedDistinctUntilChanged
9 | import io.reactivex.Observable
10 | import io.reactivex.disposables.Disposable
11 | import java.util.concurrent.atomic.AtomicReferenceArray
12 |
13 | class AndroidEditText(
14 | private val realEditText: android.widget.EditText,
15 | private val renderer: Renderer
16 | ) : EditText {
17 |
18 | companion object {
19 | private const val STATE_INDEX_SELECTION = 0
20 | }
21 |
22 | private val asTextView: TextView = AndroidTextView(realEditText, renderer)
23 | private val state = AtomicReferenceArray(1)
24 |
25 | override val observe: EditText.Observe = object : EditText.Observe, TextView.Observe by asTextView.observe {
26 |
27 | }
28 |
29 | override val change: EditText.Change = object : EditText.Change, TextView.Change by asTextView.change {
30 |
31 | override fun selection(selectionValues: Observable): Disposable {
32 | return selectionValues
33 | .sharedDistinctUntilChanged(state, STATE_INDEX_SELECTION)
34 | .mapToChange(realEditText, STATE_INDEX_SELECTION) { realEditText.setSelection(it) }
35 | .subscribe(renderer::render)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/samples/mvp/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | buildToolsVersion versions.buildTools
6 |
7 | defaultConfig {
8 | applicationId 'com.lyf.domic.samples.mvp'
9 | compileSdkVersion versions.compileSdk
10 | targetSdkVersion versions.targetSdk
11 | minSdkVersion versions.minSdk
12 | versionCode 1
13 | versionName '0.0.1'
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled true
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 |
23 | lintOptions {
24 | disable 'GoogleAppIndexingWarning'
25 | }
26 | }
27 |
28 | // See `dependencies.gradle`.
29 | dependencies {
30 | implementation project(':domic:api')
31 | implementation project(':domic:android')
32 |
33 | implementation libraries.kotlinStdlib
34 |
35 | implementation supportLibraries.appCompat
36 | implementation supportLibraries.constraintLayout
37 |
38 | implementation libraries.rxJava
39 | implementation libraries.rxAndroid
40 | implementation libraries.rxKotlin
41 | implementation libraries.rxReplayingShareKotlin
42 | implementation libraries.koptionalRxJava2
43 |
44 | implementation project(':samples:shared')
45 | }
46 |
47 | dependencies {
48 | testImplementation project(':domic:test')
49 | testImplementation testLibraries.junit
50 | testImplementation testLibraries.assertJ
51 | testImplementation testLibraries.kotlinMockito
52 | testImplementation libraries.rxRelay
53 | }
54 |
--------------------------------------------------------------------------------
/samples/mvvm/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | buildToolsVersion versions.buildTools
6 |
7 | defaultConfig {
8 | applicationId 'com.lyf.domic.samples.mvvm'
9 | compileSdkVersion versions.compileSdk
10 | targetSdkVersion versions.targetSdk
11 | minSdkVersion versions.minSdk
12 | versionCode 1
13 | versionName '0.0.1'
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled true
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 |
23 | lintOptions {
24 | disable 'GoogleAppIndexingWarning'
25 | }
26 | }
27 |
28 | // See `dependencies.gradle`.
29 | dependencies {
30 | implementation project(':domic:api')
31 | implementation project(':domic:android')
32 |
33 | implementation libraries.kotlinStdlib
34 |
35 | implementation supportLibraries.appCompat
36 | implementation supportLibraries.constraintLayout
37 |
38 | implementation libraries.rxJava
39 | implementation libraries.rxAndroid
40 | implementation libraries.rxKotlin
41 | implementation libraries.rxReplayingShareKotlin
42 | implementation libraries.koptionalRxJava2
43 |
44 | implementation project(':samples:shared')
45 | }
46 |
47 | dependencies {
48 | testImplementation project(':domic:test')
49 | testImplementation testLibraries.junit
50 | testImplementation testLibraries.assertJ
51 | testImplementation testLibraries.kotlinMockito
52 | testImplementation libraries.rxRelay
53 | }
54 |
--------------------------------------------------------------------------------
/domic/test/src/main/java/com/lyft/domic/test/TestCompoundButton.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.test
2 |
3 | import com.lyft.domic.api.Button
4 | import com.lyft.domic.api.CompoundButton
5 | import com.lyft.domic.util.sharedDistinctUntilChanged
6 | import io.reactivex.Observable
7 | import io.reactivex.disposables.Disposable
8 | import java.util.concurrent.atomic.AtomicReferenceArray
9 |
10 | class TestCompoundButton : CompoundButton {
11 |
12 | interface Check {
13 | val checked: Boolean?
14 | }
15 |
16 | interface Simulate {
17 | fun checked(checked: Boolean)
18 | }
19 |
20 | private val asTestButton = TestButton()
21 |
22 | private val checkedRelay = TestPublishRelay.create()
23 |
24 | override val observe: CompoundButton.Observe = object : CompoundButton.Observe, Button.Observe by asTestButton.observe {
25 | override val checked: Observable = checkedRelay
26 | }
27 |
28 | override val change: CompoundButton.Change = object : CompoundButton.Change, Button.Change by asTestButton.change {
29 |
30 | private val state = AtomicReferenceArray(1)
31 |
32 | override fun checked(checkedValues: Observable): Disposable = checkedValues
33 | .sharedDistinctUntilChanged(state, 0)
34 | .subscribe(checkedRelay)
35 | }
36 |
37 | val check: Check = object : Check {
38 | override val checked: Boolean?
39 | get() = checkedRelay.lastValue()
40 | }
41 |
42 | val simulate: Simulate = object : Simulate {
43 | override fun checked(checked: Boolean) = checkedRelay.accept(checked)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/samples/redux/rxredux/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | buildToolsVersion versions.buildTools
6 |
7 | defaultConfig {
8 | applicationId 'com.lyf.domic.samples.redux.rxredux'
9 | compileSdkVersion versions.compileSdk
10 | targetSdkVersion versions.targetSdk
11 | minSdkVersion versions.minSdk
12 | versionCode 1
13 | versionName '0.0.1'
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled true
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 |
23 | lintOptions {
24 | disable 'GoogleAppIndexingWarning'
25 | }
26 | }
27 |
28 | // See `dependencies.gradle`.
29 | dependencies {
30 | implementation project(':domic:api')
31 | implementation project(':domic:android')
32 |
33 | implementation libraries.kotlinStdlib
34 |
35 | implementation supportLibraries.appCompat
36 | implementation supportLibraries.constraintLayout
37 |
38 | implementation libraries.rxJava
39 | implementation libraries.rxAndroid
40 | implementation libraries.rxKotlin
41 | implementation libraries.rxReplayingShareKotlin
42 | implementation libraries.koptionalRxJava2
43 | implementation sampleLibraries.rxRedux
44 |
45 | implementation project(':samples:shared')
46 | }
47 |
48 | dependencies {
49 | testImplementation project(':domic:test')
50 | testImplementation testLibraries.junit
51 | testImplementation testLibraries.assertJ
52 | testImplementation testLibraries.kotlinMockito
53 | testImplementation libraries.rxRelay
54 | }
55 |
--------------------------------------------------------------------------------
/samples/performance/src/main/java/com/lyft/domic/samples/performance/domic/AndroidDomicView.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.performance.domic
2 |
3 | import android.view.ViewGroup
4 | import com.lyft.domic.android.AndroidButton
5 | import com.lyft.domic.android.AndroidCompoundButton
6 | import com.lyft.domic.android.AndroidTextView
7 | import com.lyft.domic.api.rendering.Renderer
8 | import com.lyft.domic.samples.performance.R
9 |
10 | class AndroidDomicView(root: ViewGroup, renderer: Renderer) : DomicView {
11 |
12 | override val counter0 = AndroidTextView(root.findViewById(R.id.counter0), renderer)
13 | override val counter1 = AndroidTextView(root.findViewById(R.id.counter1), renderer)
14 | override val counter2 = AndroidTextView(root.findViewById(R.id.counter2), renderer)
15 | override val counter3 = AndroidTextView(root.findViewById(R.id.counter3), renderer)
16 | override val counter4 = AndroidTextView(root.findViewById(R.id.counter4), renderer)
17 | override val counter5 = AndroidTextView(root.findViewById(R.id.counter5), renderer)
18 | override val counter6 = AndroidTextView(root.findViewById(R.id.counter6), renderer)
19 | override val counter7 = AndroidTextView(root.findViewById(R.id.counter7), renderer)
20 | override val counter8 = AndroidTextView(root.findViewById(R.id.counter8), renderer)
21 | override val counter9 = AndroidTextView(root.findViewById(R.id.counter9), renderer)
22 |
23 | override val button = AndroidButton(root.findViewById(R.id.button), renderer)
24 | override val checkBox = AndroidCompoundButton(root.findViewById(R.id.checkbox), renderer)
25 | override val radioButton = AndroidCompoundButton(root.findViewById(R.id.radiobutton), renderer)
26 | }
27 |
--------------------------------------------------------------------------------
/domic/test/src/main/java/com/lyft/domic/test/TestEditText.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.test
2 |
3 | import com.lyft.domic.api.EditText
4 | import com.lyft.domic.api.TextView
5 | import com.lyft.domic.util.sharedDistinctUntilChanged
6 |
7 | import io.reactivex.Observable
8 | import io.reactivex.disposables.Disposable
9 | import java.util.concurrent.atomic.AtomicReferenceArray
10 |
11 | class TestEditText : EditText {
12 |
13 | interface Check : TestTextView.Check {
14 |
15 | val selection: Int?
16 | }
17 |
18 | interface Simulate : TestTextView.Simulate {
19 |
20 | fun selection(selection: Int)
21 | }
22 |
23 | private val asTestTextView = TestTextView()
24 |
25 | private val selectionRelay = TestPublishRelay.create()
26 |
27 | override val observe: EditText.Observe = object : EditText.Observe, TextView.Observe by asTestTextView.observe {
28 |
29 | }
30 |
31 | override val change: EditText.Change = object : EditText.Change, TextView.Change by asTestTextView.change {
32 |
33 | private val state = AtomicReferenceArray(1)
34 |
35 | override fun selection(selectionValues: Observable): Disposable =
36 | selectionValues
37 | .sharedDistinctUntilChanged(state, 0)
38 | .subscribe(selectionRelay)
39 | }
40 |
41 | val check: Check = object : Check, TestTextView.Check by asTestTextView.check {
42 |
43 | override val selection: Int?
44 | get() = selectionRelay.lastValue()
45 | }
46 |
47 | val simulate: Simulate = object : Simulate, TestTextView.Simulate by asTestTextView.simulate {
48 |
49 | override fun selection(selection: Int) {
50 | selectionRelay.accept(selection)
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/samples/shared/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
31 |
32 |
41 |
42 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/domic/android/src/main/java/com/lyft/domic/android/rendering/RenderingBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.android.rendering
2 |
3 | /**
4 | * Rendering buffer, all methods must be thread-safe.
5 | *
6 | * See https://en.wikipedia.org/wiki/Multiple_buffering#Double_buffering_in_computer_graphics
7 | */
8 | interface RenderingBuffer {
9 |
10 | /**
11 | * Adds or replaces an item in *current* underlying buffer. Relies on item's [equals].
12 | *
13 | * In case of replacement item should be added to the tail of the buffer.
14 | */
15 | fun addOrReplace(item: T)
16 |
17 | /**
18 | * Checks if *current* underlying buffer empty.
19 | */
20 | fun isEmpty(): Boolean
21 |
22 | /**
23 | * Atomically swaps *current* underlying buffer with another and returns the one that was
24 | * *current* before the swap.
25 | *
26 | * Subsequent reads and writes on this [RenderingBuffer] will work against another buffer
27 | * until it's swapped.
28 | * "Another" buffer is guaranteed to be empty.
29 | *
30 | * - Returned buffer must not be modified.
31 | * - Returned buffer should be recycled after use via [recycle].
32 | * - Returned buffer is safe to read if it's not yet recycled via [recycle].
33 | *
34 | * See [Double Buffering in Computer Graphics](https://en.wikipedia.org/wiki/Multiple_buffering#Double_buffering_in_computer_graphics).
35 | *
36 | * @return read-only view to *current* underlying buffer.
37 | */
38 | fun getAndSwap(): Collection
39 |
40 | /**
41 | * Removes item from *current* underlying buffer. Relies on item's [equals].
42 | */
43 | fun remove(item: T)
44 |
45 | /**
46 | * Removes items from *current* underlying buffer. Relies on item's [equals].
47 | */
48 | fun remove(items: Collection)
49 |
50 | /**
51 | * Recycles used buffer into internal pool, it's illegal to use buffer after recycling.
52 | */
53 | fun recycle(buffer: Collection)
54 | }
55 |
--------------------------------------------------------------------------------
/domic/android/src/main/java/com/lyft/domic/android/AndroidCompoundButton.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.android
2 |
3 | import com.jakewharton.rxbinding2.widget.RxCompoundButton
4 | import com.lyft.domic.android.annotations.MutatedByFramework
5 | import com.lyft.domic.android.rendering.mapToChange
6 | import com.lyft.domic.api.Button
7 | import com.lyft.domic.api.CompoundButton
8 | import com.lyft.domic.api.rendering.Renderer
9 | import com.lyft.domic.api.subscribe
10 | import com.lyft.domic.util.sharedDistinctUntilChanged
11 | import io.reactivex.Observable
12 | import io.reactivex.android.schedulers.AndroidSchedulers.mainThread
13 | import io.reactivex.disposables.Disposable
14 | import java.util.concurrent.atomic.AtomicReferenceArray
15 |
16 | class AndroidCompoundButton(
17 | private val realCompoundButton: android.widget.CompoundButton,
18 | private val renderer: Renderer
19 | ) : CompoundButton {
20 |
21 | companion object {
22 | private const val STATE_INDEX_CHECKED = 0
23 | }
24 |
25 | private val asButton = AndroidButton(realCompoundButton, renderer)
26 | private val state = AtomicReferenceArray(1)
27 |
28 | override val observe: CompoundButton.Observe = object : CompoundButton.Observe, Button.Observe by asButton.observe {
29 |
30 | @MutatedByFramework
31 | override val checked: Observable =
32 | RxCompoundButton
33 | .checkedChanges(realCompoundButton)
34 | .subscribeOn(mainThread())
35 | .share()
36 |
37 | }
38 |
39 | override val change: CompoundButton.Change = object : CompoundButton.Change, Button.Change by asButton.change {
40 |
41 | override fun checked(checkedValues: Observable): Disposable = checkedValues
42 | .sharedDistinctUntilChanged(state, STATE_INDEX_CHECKED)
43 | .mapToChange(realCompoundButton, STATE_INDEX_CHECKED) { realCompoundButton.isChecked = it }
44 | .subscribe(renderer::render)
45 | }
46 |
47 | init {
48 | observe
49 | .checked
50 | .subscribe { state.set(STATE_INDEX_CHECKED, it) }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/domic/test/src/main/java/com/lyft/domic/test/TestTextView.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.test
2 |
3 | import com.lyft.domic.api.TextView
4 | import com.lyft.domic.api.View
5 | import com.lyft.domic.util.sharedDistinctUntilChanged
6 | import io.reactivex.Observable
7 | import io.reactivex.disposables.Disposable
8 | import java.util.concurrent.atomic.AtomicReferenceArray
9 |
10 | class TestTextView : TextView {
11 |
12 | interface Check : TestView.Check {
13 |
14 | val text: CharSequence?
15 | }
16 |
17 | interface Simulate : TestView.Simulate {
18 |
19 | fun text(text: T)
20 | }
21 |
22 | private val asTestView = TestView()
23 |
24 | private val textChangesRelay = TestPublishRelay.create()
25 | private val textChangeEventsRelay = TestPublishRelay.create()
26 |
27 | override val observe: TextView.Observe = object : TextView.Observe, View.Observe by asTestView.observe {
28 |
29 | override val textChanges: Observable = textChangesRelay
30 |
31 | override val textChangeEvents: Observable = textChangeEventsRelay
32 | }
33 |
34 | override val change: TextView.Change = object : TextView.Change, View.Change by asTestView.change {
35 |
36 | private val state = AtomicReferenceArray(1)
37 |
38 | override fun text(textValues: Observable): Disposable {
39 | return textValues
40 | .sharedDistinctUntilChanged(state, 0)
41 | .subscribe { text ->
42 | textChangesRelay.accept(text)
43 | textChangeEventsRelay.accept(Any())
44 | }
45 | }
46 | }
47 |
48 | val check: Check = object : Check, TestView.Check by asTestView.check {
49 |
50 | override val text: CharSequence?
51 | get() = textChangesRelay.lastValue()
52 | }
53 |
54 | val simulate: Simulate = object : Simulate, TestView.Simulate by asTestView.simulate {
55 |
56 | override fun text(text: T) {
57 | textChangesRelay.accept(text)
58 | textChangeEventsRelay.accept(Any())
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/domic/android/src/main/java/com/lyft/domic/android/rendering/RenderingBufferImpl.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.android.rendering
2 |
3 | import java.util.concurrent.locks.ReadWriteLock
4 | import java.util.concurrent.locks.ReentrantReadWriteLock
5 | import kotlin.concurrent.withLock
6 |
7 | internal class RenderingBufferImpl : RenderingBuffer {
8 |
9 | private val lock: ReadWriteLock = ReentrantReadWriteLock()
10 | private val bufferPool = ArrayList>(3)
11 | private var currentBuffer: MutableCollection = obtainBuffer()
12 |
13 | override fun addOrReplace(item: T) {
14 | lock.writeLock().withLock {
15 | currentBuffer.remove(item)
16 | currentBuffer.add(item)
17 | }
18 | }
19 |
20 | override fun isEmpty(): Boolean {
21 | return lock.readLock().withLock {
22 | val result = currentBuffer.isEmpty()
23 | result
24 | }
25 | }
26 |
27 | override fun getAndSwap(): Collection {
28 | return lock.writeLock().withLock {
29 | val snapshot = currentBuffer
30 | currentBuffer = obtainBuffer()
31 |
32 | snapshot
33 | }
34 | }
35 |
36 | override fun remove(item: T) {
37 | lock.writeLock().withLock {
38 | currentBuffer.remove(item)
39 | }
40 | }
41 |
42 | override fun remove(items: Collection) {
43 | lock.writeLock().withLock {
44 | currentBuffer.removeAll(items)
45 | }
46 | }
47 |
48 | override fun recycle(buffer: Collection) {
49 | lock.writeLock().withLock {
50 | buffer as MutableCollection
51 | buffer.clear()
52 | bufferPool.add(buffer)
53 | }
54 | }
55 |
56 | private fun obtainBuffer(): MutableCollection {
57 | return lock.writeLock().withLock {
58 | if (bufferPool.isEmpty()) {
59 | ArrayList(20)
60 | } else {
61 | // Poll last item to avoid array copy inside ArrayList.
62 | val index = bufferPool.size - 1
63 |
64 | val bp = bufferPool[index]
65 | bufferPool.removeAt(index)
66 |
67 | bp
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/samples/performance/src/main/java/com/lyft/domic/samples/performance/regular/AndroidRegularView.kt:
--------------------------------------------------------------------------------
1 | package com.lyft.domic.samples.performance.regular
2 |
3 | import android.view.ViewGroup
4 | import android.widget.Button
5 | import android.widget.CheckBox
6 | import android.widget.RadioButton
7 | import android.widget.TextView
8 | import com.lyft.domic.samples.performance.R
9 |
10 | class AndroidRegularView(root: ViewGroup) : RegularView {
11 |
12 | private val counter0 = root.findViewById(R.id.counter0)
13 | private val counter1 = root.findViewById(R.id.counter1)
14 | private val counter2 = root.findViewById(R.id.counter2)
15 | private val counter3 = root.findViewById(R.id.counter3)
16 | private val counter4 = root.findViewById(R.id.counter4)
17 | private val counter5 = root.findViewById(R.id.counter5)
18 | private val counter6 = root.findViewById(R.id.counter6)
19 | private val counter7 = root.findViewById(R.id.counter7)
20 | private val counter8 = root.findViewById(R.id.counter8)
21 | private val counter9 = root.findViewById(R.id.counter9)
22 |
23 | private val button = root.findViewById