diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/CHANGELOG.MD b/CHANGELOG.MD
new file mode 100644
index 0000000..fe05126
--- /dev/null
+++ b/CHANGELOG.MD
@@ -0,0 +1,6 @@
+# Changelog
+
+## [Unreleased]
+- PublishLiveData and ReplayLiveData implemented
+- Initial commit
+
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 0000000..ac8494d
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) [2019] [IBRAHIM YILMAZ cs.ibrahimyilmaz@gmail.com]
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/arch_data.iml b/arch_data.iml
new file mode 100644
index 0000000..ca1d597
--- /dev/null
+++ b/arch_data.iml
@@ -0,0 +1,211 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..2a0b9a6
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,52 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
+
+android {
+ compileSdkVersion 28
+
+
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ }
+}
+
+dependencies {
+ def lifecycle_version = "2.0.0"
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation 'androidx.appcompat:appcompat:1.0.0'
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
+ implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
+ annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
+ testImplementation "org.mockito:mockito-core:$versionMockito"
+ testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$versionMockitoKotlin"
+ kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test:runner:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
+ testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
+}
+repositories {
+ mavenCentral()
+}
diff --git a/proguard-rules.pro b/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/src/androidTest/java/me/ibrahimyilmaz/archrelay/ExampleInstrumentedTest.kt b/src/androidTest/java/me/ibrahimyilmaz/archrelay/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..b5759e4
--- /dev/null
+++ b/src/androidTest/java/me/ibrahimyilmaz/archrelay/ExampleInstrumentedTest.kt
@@ -0,0 +1,27 @@
+package me.ibrahimyilmaz.archrelay
+
+import android.content.Context
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import androidx.test.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+
+import org.junit.Assert.assertEquals
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see [Testing documentation](http://d.android.com/tools/testing)
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getTargetContext()
+
+ assertEquals("me.ibrahimyilmaz.archrelay.test", appContext.packageName)
+ }
+}
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2e40329
--- /dev/null
+++ b/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/src/main/java/me/ibrahimyilmaz/arch_data/LiveDataExtensions.kt b/src/main/java/me/ibrahimyilmaz/arch_data/LiveDataExtensions.kt
new file mode 100644
index 0000000..e7cc579
--- /dev/null
+++ b/src/main/java/me/ibrahimyilmaz/arch_data/LiveDataExtensions.kt
@@ -0,0 +1,13 @@
+package me.ibrahimyilmaz.arch_data
+
+import androidx.lifecycle.MutableLiveData
+
+
+inline fun mutableLiveDataOf(): MutableLiveData = MutableLiveData()
+
+inline fun publishLiveDataOf(): PublishLiveData = PublishLiveData()
+
+inline fun replayLiveDataOf(): ReplayLiveData = ReplayLiveData()
+
+
+
diff --git a/src/main/java/me/ibrahimyilmaz/arch_data/MemoryLessLiveData.kt b/src/main/java/me/ibrahimyilmaz/arch_data/MemoryLessLiveData.kt
new file mode 100644
index 0000000..a6a3c34
--- /dev/null
+++ b/src/main/java/me/ibrahimyilmaz/arch_data/MemoryLessLiveData.kt
@@ -0,0 +1,46 @@
+package me.ibrahimyilmaz.arch_data
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+
+internal class MemoryLessLiveData : MutableLiveData() {
+
+ private val singleDataEvent = mutableLiveDataOf>()
+
+ private val observerMap = mutableMapOf, SingleLiveEventObserver>()
+
+ private val observers: MutableCollection> get() = observerMap.keys
+
+ override fun observe(owner: LifecycleOwner, observer: Observer) {
+ val singleEventObserver = singleEventObserverOf(observer)
+ observerMap[observer] = singleEventObserver
+ singleDataEvent.observe(owner, singleEventObserver)
+ }
+
+ override fun observeForever(observer: Observer) {
+ val singleEventObserver = singleEventObserverOf(observer)
+ observerMap[observer] = singleEventObserver
+ singleDataEvent.observeForever(singleEventObserver)
+ }
+
+ override fun removeObserver(observer: Observer) {
+ observerMap.remove(observer)?.let {
+ singleDataEvent.removeObserver(it)
+ }
+ }
+
+ override fun removeObservers(owner: LifecycleOwner) {
+ observerMap.clear()
+ singleDataEvent.removeObservers(owner)
+ }
+
+ override fun postValue(value: T) =
+ singleDataEvent.postValue(SingleLiveDataEvent(observers, value))
+
+ override fun setValue(value: T) {
+ singleDataEvent.value = SingleLiveDataEvent(observers, value)
+ }
+
+}
+
diff --git a/src/main/java/me/ibrahimyilmaz/arch_data/PublishLiveData.kt b/src/main/java/me/ibrahimyilmaz/arch_data/PublishLiveData.kt
new file mode 100644
index 0000000..c569e1a
--- /dev/null
+++ b/src/main/java/me/ibrahimyilmaz/arch_data/PublishLiveData.kt
@@ -0,0 +1,38 @@
+package me.ibrahimyilmaz.arch_data
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+
+class PublishLiveData : MutableLiveData() {
+
+ private val liveData = MemoryLessLiveData()
+
+ override fun observe(owner: LifecycleOwner, observer: Observer) {
+ liveData.observe(owner, observer)
+ }
+
+ override fun observeForever(observer: Observer) {
+ liveData.observeForever(observer)
+ }
+
+ override fun removeObserver(observer: Observer) {
+ liveData.removeObserver(observer)
+ }
+
+ override fun removeObservers(owner: LifecycleOwner) {
+ liveData.removeObservers(owner)
+ }
+
+ override fun postValue(value: T) {
+ liveData.postValue(value)
+ }
+
+ override fun setValue(value: T) {
+ liveData.setValue(value)
+ }
+
+ override fun getValue(): T? {
+ return liveData.value
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/ibrahimyilmaz/arch_data/ReplayLiveData.kt b/src/main/java/me/ibrahimyilmaz/arch_data/ReplayLiveData.kt
new file mode 100644
index 0000000..3e9a615
--- /dev/null
+++ b/src/main/java/me/ibrahimyilmaz/arch_data/ReplayLiveData.kt
@@ -0,0 +1,50 @@
+package me.ibrahimyilmaz.arch_data
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+
+class ReplayLiveData : MutableLiveData() {
+
+ private val liveData = MemoryLessLiveData()
+
+ private val values = mutableListOf()
+
+ private val mutableDataEvent = mutableLiveDataOf>()
+
+ private val eventObserver: (Observer) -> Unit = { observer ->
+ values.forEach { observer.onChanged(it) }
+ }
+
+ init {
+ mutableDataEvent.observeForever(eventObserver)
+ }
+
+ override fun observe(owner: LifecycleOwner, observer: Observer) {
+ liveData.observe(owner, observer)
+ mutableDataEvent.postValue(observer)
+ }
+
+ override fun observeForever(observer: Observer) {
+ liveData.observeForever(observer)
+ mutableDataEvent.postValue(observer)
+ }
+
+ override fun postValue(value: T) {
+ values.add(value)
+ liveData.postValue(value)
+ }
+
+ override fun setValue(value: T) {
+ values.add(value)
+ liveData.setValue(value)
+ }
+
+ override fun getValue(): T? {
+ return liveData.value
+ }
+
+ protected fun finalize() {
+ mutableDataEvent.removeObserver(eventObserver)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/ibrahimyilmaz/arch_data/SingleEventObserver.kt b/src/main/java/me/ibrahimyilmaz/arch_data/SingleEventObserver.kt
new file mode 100644
index 0000000..35d89c2
--- /dev/null
+++ b/src/main/java/me/ibrahimyilmaz/arch_data/SingleEventObserver.kt
@@ -0,0 +1,15 @@
+package me.ibrahimyilmaz.arch_data
+
+import androidx.lifecycle.Observer
+
+
+internal inline fun singleEventObserverOf(observer: Observer): SingleLiveEventObserver = SingleLiveEventObserver(observer)
+
+internal class SingleLiveEventObserver(private val observer: Observer) : Observer> {
+ override fun onChanged(t: SingleLiveDataEvent?) {
+ t?.getContentIfNotHandled(observer)?.let {
+ observer.onChanged(it)
+ }
+ }
+}
+
diff --git a/src/main/java/me/ibrahimyilmaz/arch_data/SingleLiveDataEvent.kt b/src/main/java/me/ibrahimyilmaz/arch_data/SingleLiveDataEvent.kt
new file mode 100644
index 0000000..c8a45b6
--- /dev/null
+++ b/src/main/java/me/ibrahimyilmaz/arch_data/SingleLiveDataEvent.kt
@@ -0,0 +1,17 @@
+package me.ibrahimyilmaz.arch_data
+
+import androidx.lifecycle.Observer
+
+
+internal class SingleLiveDataEvent(observers: MutableCollection>, private val content: T) {
+
+ private val receiverObservers = ArrayList(observers)
+
+ fun getContentIfNotHandled(observer: Observer): T? =
+ receiverObservers.firstOrNull { it == observer }?.let {
+ receiverObservers.remove(it)
+ content
+ }
+}
+
+
diff --git a/src/test/java/me/ibrahimyilmaz/arch_data/PublishLiveDataTest.kt b/src/test/java/me/ibrahimyilmaz/arch_data/PublishLiveDataTest.kt
new file mode 100644
index 0000000..fd0e660
--- /dev/null
+++ b/src/test/java/me/ibrahimyilmaz/arch_data/PublishLiveDataTest.kt
@@ -0,0 +1,146 @@
+package me.ibrahimyilmaz.arch_data
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.Observer
+import com.nhaarman.mockitokotlin2.*
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestRule
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnitRunner
+
+@RunWith(MockitoJUnitRunner::class)
+class PublishLiveDataTest {
+
+ @get:Rule
+ var rule: TestRule = InstantTaskExecutorRule()
+
+ private lateinit var liveData: PublishLiveData
+
+ @Before
+ fun setUp() {
+ liveData = publishLiveDataOf()
+ }
+
+ @Test
+ fun should_not_notify_when_there_is_no_event() {
+ //GIVEN
+ val observer = mock>()
+
+ //WHEN
+ liveData.observeForever(observer)
+
+ //THEN
+ verify(observer, never()).onChanged(any())
+ }
+
+ @Test
+ fun should_not_notify_when_there_is_event_before_observation() {
+ //GIVEN
+ val observer = mock>()
+
+ //WHEN
+ liveData.postValue(5)
+ liveData.observeForever(observer)
+
+ //THEN
+ verify(observer, never()).onChanged(any())
+ }
+
+ @Test
+ fun should_notify_when_there_is_event() {
+ //GIVEN
+ val expectedValue = 5
+ val observer = mock>()
+
+ //WHEN
+ liveData.observeForever(observer)
+ liveData.postValue(expectedValue)
+
+ //THEN
+ verify(observer).onChanged(expectedValue)
+ }
+
+ @Test
+ fun should_not_notify_new_observer_for_previous_events() {
+ //GIVEN
+ val observerOld = mock>()
+ val observerNew = mock>()
+
+ //WHEN
+ liveData.observeForever(observerOld)
+
+ liveData.postValue(1)
+ liveData.postValue(2)
+ liveData.postValue(3)
+
+ liveData.observeForever(observerNew)
+
+ //THEN
+ verify(observerNew, never()).onChanged(1)
+ verify(observerNew, never()).onChanged(2)
+ verify(observerNew, never()).onChanged(3)
+ }
+
+ @Test
+ fun should_notify_new_observers_for_only_new_events() {
+ //GIVEN
+ val observerOld = mock>()
+ val observerNew = mock>()
+
+ //WHEN
+ liveData.observeForever(observerOld)
+ liveData.postValue(1)
+ liveData.postValue(2)
+ liveData.postValue(3)
+
+ liveData.observeForever(observerNew)
+ liveData.postValue(4)
+ liveData.postValue(5)
+
+ //THEN
+ verify(observerNew, never()).onChanged(1)
+ verify(observerNew, never()).onChanged(2)
+ verify(observerNew, never()).onChanged(3)
+ val inOrderVerifier = inOrder(observerNew)
+ inOrderVerifier.verify(observerNew).onChanged(4)
+ inOrderVerifier.verify(observerNew).onChanged(5)
+ inOrderVerifier.verifyNoMoreInteractions()
+ }
+
+ @Test
+ fun should_receive_events_happening_after_subscription_for_re_subscription() {
+ //GIVEN
+ val observer = mock>()
+
+ //WHEN
+ liveData.observeForever(observer)
+
+ liveData.postValue(1)
+ liveData.postValue(2)
+
+
+ liveData.removeObserver(observer)
+
+ liveData.postValue(3)
+ liveData.postValue(4)
+
+ liveData.observeForever(observer)
+
+ liveData.postValue(5)
+ liveData.postValue(6)
+
+ //THEN
+ verify(observer, never()).onChanged(3)
+ verify(observer, never()).onChanged(4)
+
+ val inOrderVerifier = inOrder(observer)
+ inOrderVerifier.verify(observer).onChanged(1)
+ inOrderVerifier.verify(observer).onChanged(2)
+ inOrderVerifier.verify(observer).onChanged(5)
+ inOrderVerifier.verify(observer).onChanged(6)
+ inOrderVerifier.verifyNoMoreInteractions()
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/me/ibrahimyilmaz/arch_data/ReplayLiveDataTest.kt b/src/test/java/me/ibrahimyilmaz/arch_data/ReplayLiveDataTest.kt
new file mode 100644
index 0000000..82c200b
--- /dev/null
+++ b/src/test/java/me/ibrahimyilmaz/arch_data/ReplayLiveDataTest.kt
@@ -0,0 +1,99 @@
+package me.ibrahimyilmaz.arch_data
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.Observer
+import com.nhaarman.mockitokotlin2.inOrder
+import com.nhaarman.mockitokotlin2.mock
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestRule
+
+class ReplayLiveDataTest {
+
+ @get:Rule
+ var rule: TestRule = InstantTaskExecutorRule()
+
+ private lateinit var liveData: ReplayLiveData
+
+ @Before
+ fun setUp() {
+ liveData = replayLiveDataOf()
+ }
+
+ @Test
+ fun should_notify_new_observers_for_past_events() {
+ //GIVEN
+ val observerOld = mock>()
+ val observerNew = mock>()
+
+ //WHEN
+ liveData.observeForever(observerOld)
+ liveData.postValue(1)
+ liveData.postValue(2)
+ liveData.postValue(3)
+
+ liveData.observeForever(observerNew)
+ val inOrderVerifier = inOrder(observerNew)
+ inOrderVerifier.verify(observerNew).onChanged(1)
+ inOrderVerifier.verify(observerNew).onChanged(2)
+ inOrderVerifier.verify(observerNew).onChanged(3)
+ inOrderVerifier.verifyNoMoreInteractions()
+ }
+
+ @Test
+ fun should_notify_observer_for_past_events_for_re_subscription() {
+ //GIVEN
+ val observer = mock>()
+
+ //WHEN
+ liveData.observeForever(observer)
+ liveData.postValue(1)
+ liveData.postValue(2)
+ liveData.postValue(3)
+
+ liveData.removeObserver(observer)
+ liveData.observeForever(observer)
+
+ val inOrderVerifier = inOrder(observer)
+ inOrderVerifier.verify(observer).onChanged(1)
+ inOrderVerifier.verify(observer).onChanged(2)
+ inOrderVerifier.verify(observer).onChanged(3)
+ inOrderVerifier.verify(observer).onChanged(1)
+ inOrderVerifier.verify(observer).onChanged(2)
+ inOrderVerifier.verify(observer).onChanged(3)
+ inOrderVerifier.verifyNoMoreInteractions()
+ }
+
+ @Test
+ fun should_receive_events_happened_when_observer_unsubscribed_for_re_subscription() {
+ //GIVEN
+ val observer = mock>()
+
+ //WHEN
+ liveData.observeForever(observer)
+ liveData.postValue(1)
+ liveData.postValue(2)
+ liveData.postValue(3)
+
+ liveData.removeObserver(observer)
+ liveData.postValue(4)
+ liveData.postValue(5)
+ liveData.observeForever(observer)
+
+ liveData.postValue(6)
+
+ val inOrderVerifier = inOrder(observer)
+ inOrderVerifier.verify(observer).onChanged(1)
+ inOrderVerifier.verify(observer).onChanged(2)
+ inOrderVerifier.verify(observer).onChanged(3)
+ inOrderVerifier.verify(observer).onChanged(1)
+ inOrderVerifier.verify(observer).onChanged(2)
+ inOrderVerifier.verify(observer).onChanged(3)
+ inOrderVerifier.verify(observer).onChanged(4)
+ inOrderVerifier.verify(observer).onChanged(5)
+ inOrderVerifier.verify(observer).onChanged(6)
+ inOrderVerifier.verifyNoMoreInteractions()
+ }
+
+}
\ No newline at end of file