diff --git a/README.md b/README.md index 4d6402e..a0af08a 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,8 @@ dependencies { } ``` -`LATEST_VERSION` is [![JitPack](https://jitpack.io/v/Kyash/validatable-textinput-layout.svg)](https://jitpack.io/#Kyash/validatable-textinput-layout) +`LATEST_VERSION` is [![JitPack](https://jitpack.io/v/Kyash/validatable-textinput-layout.svg)](https://jitpack.io/#Kyash/validatable-textinput-layout) which supports AndroidX. +If you still use Support Library, please use version `0.3.0`. ## Basic usage You can use as same as `TextInputLayout` in layout xml. diff --git a/build.gradle b/build.gradle index aac1d71..0bb78d3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ -buildscript { - apply from: "${rootDir.absolutePath}/versions.gradle" +import dependencies.Depends +buildscript { repositories { google() jcenter() @@ -10,11 +10,11 @@ buildscript { maven { url "https://jitpack.io" } } dependencies { - classpath "com.android.tools.build:gradle:$versions.gradleBuildTool" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" - classpath "gradle.plugin.org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle" - classpath "io.fabric.tools:gradle:$versions.fabricGradleTool" - classpath "com.github.dcendents:android-maven-gradle-plugin:$versions.mavenGradle" + classpath Depends.GradlePlugin.android + classpath Depends.GradlePlugin.kotlin + classpath Depends.GradlePlugin.ktlint + classpath Depends.GradlePlugin.fabric + classpath Depends.GradlePlugin.androidMaven } } diff --git a/buildSrc/.gitignore b/buildSrc/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/buildSrc/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..15b61a2 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,6 @@ +plugins { + `kotlin-dsl` +} +repositories { + jcenter() +} \ No newline at end of file diff --git a/buildSrc/src/main/java/dependencies/Depends.kt b/buildSrc/src/main/java/dependencies/Depends.kt new file mode 100644 index 0000000..104e7f4 --- /dev/null +++ b/buildSrc/src/main/java/dependencies/Depends.kt @@ -0,0 +1,65 @@ +package dependencies + +@Suppress("unused") +object Depends { + object GradlePlugin { + const val android = "com.android.tools.build:gradle:3.2.1" + const val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Kotlin.version}" + const val ktlint = "gradle.plugin.org.jlleitschuh.gradle:ktlint-gradle:3.0.0" + const val fabric = "io.fabric.tools:gradle:1.25.4" + const val androidMaven = "com.github.dcendents:android-maven-gradle-plugin:2.0" + } + + object Test { + const val junit = "junit:junit:4.12" + const val testRunner = "androidx.test:runner:1.1.0" + const val mockitoKotlin = "com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0" + const val robolectric = "org.robolectric:robolectric:3.5.1" + + object Espresso { + const val core = "androidx.test.espresso:espresso-core:3.1.0-alpha4" + const val intents = "androidx.test.espresso:espresso-intents:3.1.0-alpha4" + } + } + + object AndroidX { + const val appCompat = "androidx.appcompat:appcompat:1.0.0" + const val recyclerView = "androidx.recyclerview:recyclerview:1.0.0" + const val cardView = "androidx.cardview:cardview:1.0.0" + const val design = "com.google.android.material:material:1.1.0-alpha01" + } + + object Kotlin { + const val version = "1.3.11" + const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$version" + } + + object Stetho { + const val version = "1.5.0" + const val core = "com.facebook.stetho:stetho:$version" + const val okhttp = "com.facebook.stetho:stetho-okhttp3:$version" + } + + object Crashlytics { + const val core = "com.crashlytics.sdk.android:crashlytics:2.8.0@aar" + } + + object Retrofit { + private const val version = "2.5.0" + const val core = "com.squareup.retrofit2:retrofit:$version" + const val converterMoshi = "com.squareup.retrofit2:converter-moshi:$version" + const val adapterRxJava2 = "com.squareup.retrofit2:adapter-rxjava2:$version" + } + + object Kotshi { + private const val version = "1.0.6" + const val api = "se.ansman.kotshi:api:$version" + const val compiler = "se.ansman.kotshi:compiler:$version" + } + + object Rx { + const val RxJava = "io.reactivex.rxjava2:rxjava:2.2.4" + const val RxAndroid = "io.reactivex.rxjava2:rxandroid:2.1.0" + const val RxKotlin = "io.reactivex.rxjava2:rxkotlin:2.3.0" + } +} \ No newline at end of file diff --git a/buildSrc/src/main/java/dependencies/Versions.kt b/buildSrc/src/main/java/dependencies/Versions.kt new file mode 100644 index 0000000..da540ce --- /dev/null +++ b/buildSrc/src/main/java/dependencies/Versions.kt @@ -0,0 +1,7 @@ +package dependencies + +private object Versions { + val androidCompileSdkVersion = 28 + val androidTargetSdkVersion = 28 + val androidMinSdkVersion = 19 +} \ No newline at end of file diff --git a/example/build.gradle b/example/build.gradle index 32c7e15..9d1639b 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -1,21 +1,25 @@ +import dependencies.Depends +import dependencies.Versions + apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: "org.jlleitschuh.gradle.ktlint" apply plugin: 'io.fabric' +apply from: "${rootDir.absolutePath}/versions.gradle" -def versionMajor = 0 -def versionMinor = 1 +def versionMajor = 1 +def versionMinor = 0 def versionPatch = 0 android { - compileSdkVersion versions.compileSdk + compileSdkVersion Versions.androidCompileSdkVersion dataBinding.enabled = true defaultConfig { applicationId "co.kyash.vtl.sample" - minSdkVersion versions.minSdk - targetSdkVersion versions.targetSdk + minSdkVersion Versions.androidMinSdkVersion + targetSdkVersion Versions.androidTargetSdkVersion versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch versionName "$versionMajor.$versionMinor.$versionPatch" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -58,43 +62,42 @@ dependencies { implementation project(':library') //==================== Kotlin ==================== - implementation depends.kotlin.stdlib + implementation Depends.Kotlin.stdlib //==================== Support Library ==================== - implementation depends.support.appcompat - implementation depends.support.design - implementation depends.support.cardview + implementation Depends.AndroidX.appCompat + implementation Depends.AndroidX.design + implementation Depends.AndroidX.cardView //==================== Network ==================== - implementation depends.retrofit.core - implementation depends.retrofit.converterMoshi - implementation depends.retrofit.adapterRxJava2 + implementation Depends.Retrofit.core + implementation Depends.Retrofit.converterMoshi + implementation Depends.Retrofit.adapterRxJava2 //==================== Structure ==================== - implementation depends.kotshi.api - kapt depends.kotshi.compiler + implementation Depends.Kotshi.api + kapt Depends.Kotshi.compiler - implementation depends.rxjava2.core - implementation depends.rxjava2.android - implementation depends.rxjava2.kotlin + implementation Depends.Rx.RxJava + implementation Depends.Rx.RxAndroid + implementation Depends.Rx.RxKotlin //==================== Debug ==================== - implementation(depends.crashlytics) { + implementation(Depends.Crashlytics.core) { transitive = true } //==================== Debug ==================== - debugImplementation depends.stetho.core - debugImplementation depends.stetho.okhttp3 + implementation Depends.Stetho.core + implementation Depends.Stetho.okhttp //==================== Test ==================== - testImplementation depends.junit - testImplementation depends.mockitoKotlin - testImplementation depends.robolectric.core - androidTestImplementation depends.supporttest.runner - androidTestImplementation depends.supporttest.espresso - androidTestImplementation depends.espresso.core - androidTestImplementation depends.espresso.intents + testImplementation Depends.Test.junit + testImplementation Depends.Test.mockitoKotlin + testImplementation Depends.Test.robolectric + androidTestImplementation Depends.Test.testRunner + androidTestImplementation Depends.Test.Espresso.core + androidTestImplementation Depends.Test.Espresso.intents } ktlint { diff --git a/example/src/main/java/co/kyash/vtl/example/MainActivity.kt b/example/src/main/java/co/kyash/vtl/example/MainActivity.kt index 29c2be6..2b70b40 100644 --- a/example/src/main/java/co/kyash/vtl/example/MainActivity.kt +++ b/example/src/main/java/co/kyash/vtl/example/MainActivity.kt @@ -1,11 +1,11 @@ package co.kyash.vtl.example -import android.databinding.DataBindingUtil import android.os.Bundle -import android.support.v7.app.AppCompatActivity import android.util.Log import android.view.View import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil import co.kyash.vtl.ValidatableView import co.kyash.vtl.example.api.MaterialDesignColorsApi import co.kyash.vtl.example.databinding.ActivityMainBinding @@ -42,12 +42,12 @@ class MainActivity : AppCompatActivity() { private val compositeDisposable = CompositeDisposable() private val api = Retrofit.Builder() - .baseUrl("https://raw.githubusercontent.com") - .addConverterFactory(MoshiConverterFactory.create(Moshi.Builder().build())) - .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()) - .client(OkHttpClient.Builder().addNetworkInterceptor(StethoInterceptor()).build()) - .build() - .create(MaterialDesignColorsApi::class.java) + .baseUrl("https://raw.githubusercontent.com") + .addConverterFactory(MoshiConverterFactory.create(Moshi.Builder().build())) + .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()) + .client(OkHttpClient.Builder().addNetworkInterceptor(StethoInterceptor()).build()) + .build() + .create(MaterialDesignColorsApi::class.java) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -65,16 +65,16 @@ class MainActivity : AppCompatActivity() { private fun initValidators() { // Example 1 validatableViewsForTriggerTextChanged.addAll(arrayOf( - binding.firstName.register(RequiredValidator(getString(R.string.validation_error_required))), - binding.lastName.register(RequiredValidator(getString(R.string.validation_error_required))), - binding.email.register(EmailValidator(getString(R.string.validation_error_email))), - binding.numberOnly.register(NumberOnlyValidator(getString(R.string.validation_error_number_only))), - binding.asciiOnly.register(AsciiOnlyValidator(getString(R.string.validation_error_ascii_only))) + binding.firstName.register(RequiredValidator(getString(R.string.validation_error_required))), + binding.lastName.register(RequiredValidator(getString(R.string.validation_error_required))), + binding.email.register(EmailValidator(getString(R.string.validation_error_email))), + binding.numberOnly.register(NumberOnlyValidator(getString(R.string.validation_error_number_only))), + binding.asciiOnly.register(AsciiOnlyValidator(getString(R.string.validation_error_ascii_only))) )) // Example 2 validatableViewsForTriggerFocusChanged.addAll(arrayOf( - binding.email2.register(EmailValidator(getString(R.string.validation_error_email))) + binding.email2.register(EmailValidator(getString(R.string.validation_error_email))) )) // Example 3 @@ -82,16 +82,16 @@ class MainActivity : AppCompatActivity() { // Example 4 validatableViewsForButtonEnable.addAll(arrayOf( - binding.firstName2.register(RequiredValidator(getString(R.string.validation_error_required))), - binding.lastName2.register(RequiredValidator(getString(R.string.validation_error_required))) + binding.firstName2.register(RequiredValidator(getString(R.string.validation_error_required))), + binding.lastName2.register(RequiredValidator(getString(R.string.validation_error_required))) )) val validations: List> = validatableViewsForButtonEnable.flatMap { it.validationFlowables } Flowable.zip(validations) { Any() } - .subscribeOn(Schedulers.computation()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError({ binding.submit3.isEnabled = false }) - .retry() // non-terminated stream - .subscribe({ binding.submit3.isEnabled = true }, { }) + .subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnError({ binding.submit3.isEnabled = false }) + .retry() // non-terminated stream + .subscribe({ binding.submit3.isEnabled = true }, { }) } @@ -113,16 +113,16 @@ class MainActivity : AppCompatActivity() { compositeDisposable.clear() compositeDisposable.add( - Completable.mergeDelayError(validations) - .subscribeOn(Schedulers.computation()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ - Log.d("MainActivity", "Validation cleared.") - Toast.makeText(this, R.string.validation_success, Toast.LENGTH_SHORT).show() - }, { throwable -> - Log.e("MainActivity", "Validation error occurred.", throwable) - Toast.makeText(this, R.string.validation_error_occurred, Toast.LENGTH_SHORT).show() - }) + Completable.mergeDelayError(validations) + .subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + Log.d("MainActivity", "Validation cleared.") + Toast.makeText(this, R.string.validation_success, Toast.LENGTH_SHORT).show() + }, { throwable -> + Log.e("MainActivity", "Validation error occurred.", throwable) + Toast.makeText(this, R.string.validation_error_occurred, Toast.LENGTH_SHORT).show() + }) ) } diff --git a/example/src/main/res/layout/activity_main.xml b/example/src/main/res/layout/activity_main.xml index 6f77bf7..ab82701 100644 --- a/example/src/main/res/layout/activity_main.xml +++ b/example/src/main/res/layout/activity_main.xml @@ -10,7 +10,7 @@ android:layout_height="match_parent" android:orientation="vertical"> - - @@ -43,12 +43,12 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:paddingBottom="@dimen/space_16dp" - android:paddingEnd="@dimen/space_16dp" + android:paddingStart="@dimen/space_16dp" android:paddingLeft="@dimen/space_16dp" + android:paddingTop="@dimen/space_8dp" + android:paddingEnd="@dimen/space_16dp" android:paddingRight="@dimen/space_16dp" - android:paddingStart="@dimen/space_16dp" - android:paddingTop="@dimen/space_8dp"> + android:paddingBottom="@dimen/space_16dp"> - + - @@ -142,12 +142,12 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:paddingBottom="@dimen/space_16dp" - android:paddingEnd="@dimen/space_16dp" + android:paddingStart="@dimen/space_16dp" android:paddingLeft="@dimen/space_16dp" + android:paddingTop="@dimen/space_8dp" + android:paddingEnd="@dimen/space_16dp" android:paddingRight="@dimen/space_16dp" - android:paddingStart="@dimen/space_16dp" - android:paddingTop="@dimen/space_8dp"> + android:paddingBottom="@dimen/space_16dp"> - + - @@ -194,12 +194,12 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:paddingBottom="@dimen/space_16dp" - android:paddingEnd="@dimen/space_16dp" + android:paddingStart="@dimen/space_16dp" android:paddingLeft="@dimen/space_16dp" + android:paddingTop="@dimen/space_8dp" + android:paddingEnd="@dimen/space_16dp" android:paddingRight="@dimen/space_16dp" - android:paddingStart="@dimen/space_16dp" - android:paddingTop="@dimen/space_8dp"> + android:paddingBottom="@dimen/space_16dp"> - + - @@ -236,12 +236,12 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:paddingBottom="@dimen/space_16dp" - android:paddingEnd="@dimen/space_16dp" + android:paddingStart="@dimen/space_16dp" android:paddingLeft="@dimen/space_16dp" + android:paddingTop="@dimen/space_8dp" + android:paddingEnd="@dimen/space_16dp" android:paddingRight="@dimen/space_16dp" - android:paddingStart="@dimen/space_16dp" - android:paddingTop="@dimen/space_8dp"> + android:paddingBottom="@dimen/space_16dp"> - + diff --git a/example/src/test/java/co/kyash/vtl/example/validators/MaterialDesignColorsValidatorTest.kt b/example/src/test/java/co/kyash/vtl/example/validators/MaterialDesignColorsValidatorTest.kt index 1bb9e3c..a764a7a 100644 --- a/example/src/test/java/co/kyash/vtl/example/validators/MaterialDesignColorsValidatorTest.kt +++ b/example/src/test/java/co/kyash/vtl/example/validators/MaterialDesignColorsValidatorTest.kt @@ -4,8 +4,8 @@ import android.content.Context import co.kyash.vtl.example.api.MaterialDesignColorsApi import co.kyash.vtl.example.testing.RxImmediateSchedulerRule import co.kyash.vtl.validators.VtlValidator -import com.nhaarman.mockito_kotlin.doReturn -import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockitokotlin2.doReturn +import com.nhaarman.mockitokotlin2.mock import io.reactivex.Single import org.junit.Before import org.junit.Ignore @@ -18,23 +18,23 @@ import org.robolectric.RuntimeEnvironment @Suppress("unused") @RunWith(ParameterizedRobolectricTestRunner::class) class MaterialDesignColorsValidatorTest( - private val text: String?, - private val errorMessage: String? + private val text: String?, + private val errorMessage: String? ) { companion object { - private val ERROR_MESSAGE = "This is not Material design color" + private const val ERROR_MESSAGE = "This is not Material design color" @JvmStatic @ParameterizedRobolectricTestRunner.Parameters fun data(): List> { return listOf( - arrayOf("Gold", ERROR_MESSAGE), - arrayOf("Blue Red", ERROR_MESSAGE), - arrayOf("Blue ", null), - arrayOf(" Blue", null), - arrayOf("Blue", null), - arrayOf("Red", null) + arrayOf("Gold", ERROR_MESSAGE), + arrayOf("Blue Red", ERROR_MESSAGE), + arrayOf("Blue ", null), + arrayOf(" Blue", null), + arrayOf("Blue", null), + arrayOf("Red", null) ) } } diff --git a/gradle.properties b/gradle.properties index 3163c1a..1c46fe6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,4 @@ org.gradle.jvmargs=-Xmx1536m -android.databinding.enableV2=true \ No newline at end of file +android.databinding.enableV2=true +android.useAndroidX=true +android.enableJetifier=true \ No newline at end of file diff --git a/library/build.gradle b/library/build.gradle index a0a8e73..459cd61 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,20 +1,23 @@ +import dependencies.Depends +import dependencies.Versions + apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'com.github.dcendents.android-maven' -def versionMajor = 0 -def versionMinor = 3 +def versionMajor = 1 +def versionMinor = 0 def versionPatch = 0 group = 'co.kyash' version = "$versionMajor.$versionMinor.$versionPatch" android { - compileSdkVersion versions.compileSdk + compileSdkVersion Versions.androidCompileSdkVersion defaultConfig { - minSdkVersion versions.minSdk - targetSdkVersion versions.targetSdk + minSdkVersion Versions.androidMinSdkVersion + targetSdkVersion Versions.androidTargetSdkVersion versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch versionName "$versionMajor.$versionMinor.$versionPatch" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -33,19 +36,19 @@ android { dependencies { //==================== Kotlin ==================== - implementation depends.kotlin.stdlib + implementation Depends.Kotlin.stdlib //==================== Support Library ==================== - implementation depends.support.appcompat - implementation depends.support.design + implementation Depends.AndroidX.appCompat + implementation Depends.AndroidX.design //==================== Structure ==================== - implementation depends.rxjava2.core + implementation Depends.Rx.RxJava //==================== Test ==================== - testImplementation depends.junit - testImplementation depends.mockitoKotlin - testImplementation depends.robolectric.core + testImplementation Depends.Test.junit + testImplementation Depends.Test.mockitoKotlin + testImplementation Depends.Test.robolectric } // build a jar with source files diff --git a/library/src/main/java/co/kyash/vtl/ValidatableTextInputLayout.kt b/library/src/main/java/co/kyash/vtl/ValidatableTextInputLayout.kt index bd929e7..1cdbbf6 100644 --- a/library/src/main/java/co/kyash/vtl/ValidatableTextInputLayout.kt +++ b/library/src/main/java/co/kyash/vtl/ValidatableTextInputLayout.kt @@ -3,7 +3,6 @@ package co.kyash.vtl import android.content.Context import android.os.Handler import android.os.Looper -import android.support.design.widget.TextInputLayout import android.text.Editable import android.text.TextUtils import android.text.TextWatcher @@ -12,6 +11,7 @@ import android.view.View import android.view.View.OnFocusChangeListener import android.view.ViewGroup import co.kyash.vtl.validators.VtlValidator +import com.google.android.material.textfield.TextInputLayout import io.reactivex.BackpressureStrategy import io.reactivex.Completable import io.reactivex.Flowable @@ -23,9 +23,9 @@ import io.reactivex.schedulers.Schedulers import java.util.concurrent.TimeUnit class ValidatableTextInputLayout @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 ) : TextInputLayout(context, attrs, defStyleAttr), ValidatableView { override val validationFlowables = ArrayList>() @@ -89,18 +89,18 @@ class ValidatableTextInputLayout @JvmOverloads constructor( shouldValidateOnTextChangedOnce = false compositeDisposable.clear() compositeDisposable.add( - Flowable.zip(validationFlowables) { Any() } - .doOnError({ this.showErrorMessage(it) }) - .retry() // non-terminated stream - .subscribeOn(Schedulers.computation()) - .subscribe({ clearErrorMessage() }, {}) + Flowable.zip(validationFlowables) { Any() } + .doOnError({ this.showErrorMessage(it) }) + .retry() // non-terminated stream + .subscribeOn(Schedulers.computation()) + .subscribe({ clearErrorMessage() }, {}) ) } } else { if (shouldValidateOnFocusChanged) { compositeDisposable.clear() compositeDisposable.add( - validateAsCompletable().subscribe(Functions.EMPTY_ACTION, Consumer {}) + validateAsCompletable().subscribe(Functions.EMPTY_ACTION, Consumer {}) ) } } @@ -164,9 +164,9 @@ class ValidatableTextInputLayout @JvmOverloads constructor( } return Completable.mergeDelayError(validations) - .doOnComplete { clearErrorMessage() } - .doOnError { showErrorMessage(it) } - .subscribeOn(Schedulers.computation()) + .doOnComplete { clearErrorMessage() } + .doOnError { showErrorMessage(it) } + .subscribeOn(Schedulers.computation()) } fun getText(): String { @@ -193,14 +193,14 @@ class ValidatableTextInputLayout @JvmOverloads constructor( this.validators.mapTo(validationFlowables) { textProcessor.onBackpressureDrop() - .throttleLast(validationInterval, TimeUnit.MILLISECONDS) - // hack to emit an event to `onNext` when completable is completed. - .flatMap { x -> - it.validateAsCompletable(context, x) - .toSingleDefault(Any()) - .toObservable() - .toFlowable(BackpressureStrategy.BUFFER) - } + .throttleLast(validationInterval, TimeUnit.MILLISECONDS) + // hack to emit an event to `onNext` when completable is completed. + .flatMap { x -> + it.validateAsCompletable(context, x) + .toSingleDefault(Any()) + .toObservable() + .toFlowable(BackpressureStrategy.BUFFER) + } } return this }