diff --git a/build.gradle b/build.gradle
index bea88a9cc..d473b7261 100644
--- a/build.gradle
+++ b/build.gradle
@@ -76,6 +76,7 @@ ext.libraries = [
// StorIO old versions
storIOSQLite1 : 'com.pushtorefresh.storio:sqlite:1.13.0',
+ storIOSQLite2 : 'com.pushtorefresh.storio2:sqlite:2.1.0',
// Libraries for tests and sample app
junit : 'junit:junit:4.12',
diff --git a/settings.gradle b/settings.gradle
index b7f591eea..91b5a66c8 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -13,6 +13,7 @@ include ':storio-content-resolver-annotations-processor'
include ':storio-content-resolver-annotations-processor-test'
include ':storio-sqlite-interop1to3'
+include ':storio-sqlite-interop2to3'
include ':storio-test-without-rxjava'
diff --git a/storio-sqlite-interop2to3/build.gradle b/storio-sqlite-interop2to3/build.gradle
new file mode 100644
index 000000000..32a9661ad
--- /dev/null
+++ b/storio-sqlite-interop2to3/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+
+ defaultConfig {
+ versionName VERSION_NAME
+ versionCode Integer.parseInt(VERSION_CODE)
+ minSdkVersion rootProject.ext.minSdkVersion
+ }
+
+ packagingOptions {
+ exclude 'LICENSE.txt' // multiple libs have this file -> cause build error
+ }
+}
+
+dependencies {
+ implementation libraries.storIOSQLite2
+ implementation libraries.storIOSQLite
+
+ // Do not use compileOnly because without rxJava and rxJava2 using interoperability makes sense.
+ implementation libraries.rxJava
+ implementation libraries.rxJava2
+
+ testImplementation libraries.junit
+ testImplementation libraries.kotlinStdLib
+ testImplementation libraries.mockitoKotlin
+ testImplementation libraries.assertJ
+}
+
+apply from: '../gradle/checkstyle.gradle'
+apply from: '../gradle/jacoco-android.gradle'
+apply from: '../gradle/publish-android-lib.gradle'
diff --git a/storio-sqlite-interop2to3/gradle.properties b/storio-sqlite-interop2to3/gradle.properties
new file mode 100644
index 000000000..8ee3f42f6
--- /dev/null
+++ b/storio-sqlite-interop2to3/gradle.properties
@@ -0,0 +1,3 @@
+POM_NAME=storio-sqlite-interop2to3
+POM_ARTIFACT_ID=storio-sqlite-interop2to3
+POM_PACKAGING=jar
diff --git a/storio-sqlite-interop2to3/src/main/AndroidManifest.xml b/storio-sqlite-interop2to3/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..3aa51a5bf
--- /dev/null
+++ b/storio-sqlite-interop2to3/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/storio-sqlite-interop2to3/src/main/java/com/pushtorefresh/storio3/sqlite/interop2to3/StorIOSQLite2To3.java b/storio-sqlite-interop2to3/src/main/java/com/pushtorefresh/storio3/sqlite/interop2to3/StorIOSQLite2To3.java
new file mode 100644
index 000000000..08ba5f044
--- /dev/null
+++ b/storio-sqlite-interop2to3/src/main/java/com/pushtorefresh/storio3/sqlite/interop2to3/StorIOSQLite2To3.java
@@ -0,0 +1,73 @@
+package com.pushtorefresh.storio3.sqlite.interop2to3;
+
+import android.support.annotation.NonNull;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import io.reactivex.BackpressureStrategy;
+import io.reactivex.functions.Consumer;
+import rx.functions.Action1;
+
+public class StorIOSQLite2To3 {
+
+ private final Set forwardedChanges2 = Collections.newSetFromMap(
+ new ConcurrentHashMap()
+ );
+
+ private final Set forwardedChanges3 = Collections.newSetFromMap(
+ new ConcurrentHashMap()
+ );
+
+ public void forwardNotifications(
+ @NonNull final com.pushtorefresh.storio2.sqlite.StorIOSQLite sqlite2,
+ @NonNull final com.pushtorefresh.storio3.sqlite.StorIOSQLite sqlite3
+ ) {
+ sqlite2.observeChanges()
+ .subscribe(new Action1() {
+ @Override
+ public void call(@NonNull com.pushtorefresh.storio2.sqlite.Changes changes2) {
+ if (!forwardedChanges2.remove(changes2)) { // Check to prevent cyclic forwarding.
+ final com.pushtorefresh.storio3.sqlite.Changes changes3ToForward
+ = convertToV3(changes2);
+ forwardedChanges3.add(changes3ToForward);
+ sqlite3.lowLevel().notifyAboutChanges(changes3ToForward);
+ }
+ }
+ });
+
+ sqlite3.observeChanges(BackpressureStrategy.BUFFER)
+ .subscribe(new Consumer() {
+ @Override
+ public void accept(com.pushtorefresh.storio3.sqlite.Changes changes3) throws Exception {
+ if (!forwardedChanges3.remove(changes3)) { // Check to prevent cyclic forwarding.
+ final com.pushtorefresh.storio2.sqlite.Changes changes2ToForward
+ = convertToV2(changes3);
+ forwardedChanges2.add(changes2ToForward);
+ sqlite2.lowLevel().notifyAboutChanges(changes2ToForward);
+ }
+ }
+ });
+ }
+
+ @NonNull
+ private com.pushtorefresh.storio2.sqlite.Changes convertToV2(
+ @NonNull com.pushtorefresh.storio3.sqlite.Changes changes3
+ ) {
+ return com.pushtorefresh.storio2.sqlite.Changes.newInstance(
+ changes3.affectedTables(),
+ changes3.affectedTags()
+ );
+ }
+
+ @NonNull
+ private com.pushtorefresh.storio3.sqlite.Changes convertToV3(
+ @NonNull com.pushtorefresh.storio2.sqlite.Changes changes2
+ ) {
+ return com.pushtorefresh.storio3.sqlite.Changes.newInstance(
+ changes2.affectedTables(),
+ changes2.affectedTags()
+ );
+ }
+}
diff --git a/storio-sqlite-interop2to3/src/test/java/com/pushtorefresh/storio3/sqlite/interop2to3/StorIOSQLite2To3Test.kt b/storio-sqlite-interop2to3/src/test/java/com/pushtorefresh/storio3/sqlite/interop2to3/StorIOSQLite2To3Test.kt
new file mode 100644
index 000000000..2a3963471
--- /dev/null
+++ b/storio-sqlite-interop2to3/src/test/java/com/pushtorefresh/storio3/sqlite/interop2to3/StorIOSQLite2To3Test.kt
@@ -0,0 +1,114 @@
+package com.pushtorefresh.storio3.sqlite.interop2to3
+
+import com.nhaarman.mockito_kotlin.mock
+import io.reactivex.BackpressureStrategy
+import org.junit.Before
+import org.junit.Test
+import com.pushtorefresh.storio2.sqlite.Changes as Changes2
+import com.pushtorefresh.storio2.sqlite.StorIOSQLite as StorIOSQLite2
+import com.pushtorefresh.storio2.sqlite.impl.DefaultStorIOSQLite as DefaultStorIOSQLite2
+import com.pushtorefresh.storio3.sqlite.Changes as Changes3
+import com.pushtorefresh.storio3.sqlite.StorIOSQLite as StorIOSQLite3
+import com.pushtorefresh.storio3.sqlite.impl.DefaultStorIOSQLite as DefaultStorIOSQLite3
+import io.reactivex.subscribers.TestSubscriber as TestSubscriberRx2
+import rx.observers.TestSubscriber as TestSubscriberRx1
+
+class StorIOSQLite2To3Test {
+
+ val observer2 = TestSubscriberRx1.create()
+
+ val observer3 = TestSubscriberRx2.create()
+
+ lateinit var storIOSQLite2: StorIOSQLite2
+
+ lateinit var storIOSQLite3: StorIOSQLite3
+
+ lateinit var changes2WithTags: Changes2
+
+ lateinit var changes2WithoutTags: Changes2
+
+ lateinit var changes3WithTags: Changes3
+
+ lateinit var changes3WithoutTags: Changes3
+
+ @Before
+ fun `before each test`() {
+ storIOSQLite2 = DefaultStorIOSQLite2.builder()
+ .sqliteOpenHelper(mock())
+ .defaultScheduler(null)
+ .build()
+ storIOSQLite3 = DefaultStorIOSQLite3.builder()
+ .sqliteOpenHelper(mock())
+ .defaultRxScheduler(null)
+ .build()
+ val interop = StorIOSQLite2To3()
+ interop.forwardNotifications(storIOSQLite2, storIOSQLite3)
+
+ storIOSQLite2.observeChanges().subscribe(observer2)
+ storIOSQLite3.observeChanges(BackpressureStrategy.BUFFER).subscribe(observer3)
+
+ val tables = HashSet()
+ tables.add("table1")
+ tables.add("table2")
+
+ val tags = HashSet()
+ tags.add("tag1")
+ tags.add("tag2")
+
+ changes2WithTags = Changes2.newInstance(tables, tags)
+ changes3WithTags = Changes3.newInstance(tables, tags)
+
+ changes2WithoutTags = Changes2.newInstance(tables, null as Collection?)
+ changes3WithoutTags = Changes3.newInstance(tables, null as Collection?)
+ }
+
+ @Test
+ fun `forwardNotifications should forward from 2 to 3 with tags`() {
+ storIOSQLite2.lowLevel().notifyAboutChanges(changes2WithTags)
+ observer2.assertValue(changes2WithTags)
+ observer3.assertValue(changes3WithTags)
+ }
+
+ @Test
+ fun `forwardNotifications should forward from 2 to 3 without tags`() {
+ storIOSQLite2.lowLevel().notifyAboutChanges(changes2WithoutTags)
+ observer2.assertValue(changes2WithoutTags)
+ observer3.assertValue(changes3WithoutTags)
+ }
+
+ @Test
+ fun `forwardNotifications should forward from 2 to 3 multiple`() {
+ storIOSQLite2.lowLevel().notifyAboutChanges(changes2WithTags)
+ observer2.assertValue(changes2WithTags)
+ observer3.assertValue(changes3WithTags)
+
+ storIOSQLite2.lowLevel().notifyAboutChanges(changes2WithTags)
+ observer2.assertValues(changes2WithTags, changes2WithTags)
+ observer3.assertValues(changes3WithTags, changes3WithTags)
+ }
+
+ @Test
+ fun `forwardNotifications should forward from 3 to 2 with tags`() {
+ storIOSQLite3.lowLevel().notifyAboutChanges(changes3WithTags)
+ observer2.assertValue(changes2WithTags)
+ observer3.assertValue(changes3WithTags)
+ }
+
+ @Test
+ fun `forwardNotifications should forward from 3 to 2 without tags`() {
+ storIOSQLite3.lowLevel().notifyAboutChanges(changes3WithoutTags)
+ observer2.assertValue(changes2WithoutTags)
+ observer3.assertValue(changes3WithoutTags)
+ }
+
+ @Test
+ fun `forwardNotifications should forward from 3 to 2 with multiple`() {
+ storIOSQLite3.lowLevel().notifyAboutChanges(changes3WithTags)
+ observer2.assertValue(changes2WithTags)
+ observer3.assertValue(changes3WithTags)
+
+ storIOSQLite3.lowLevel().notifyAboutChanges(changes3WithTags)
+ observer2.assertValues(changes2WithTags, changes2WithTags)
+ observer3.assertValues(changes3WithTags, changes3WithTags)
+ }
+}