diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a53f267fcbafc8aa9c17f611bcf5e244f9b19c79..0a7df83a40f7ff15b59d8f1e18827da6797b5d7d 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -126,6 +126,7 @@ dependencies {
   }
 
   // Utility
+  implementation("io.reactivex.rxjava2", "rxandroid", "2.0.2")
   implementation("io.reactivex.rxjava2", "rxjava", "2.1.9")
   implementation("org.threeten", "threetenbp", "1.3.6", classifier = "no-tzdb")
   implementation("org.jetbrains", "annotations", "16.0.1")
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt
index 5928f4594fa911d96c894bd96be43e0b0a815997..07a553b5cd7cb65f845c556cba8e586cf2ed0a0a 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt
@@ -106,7 +106,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
 
     setSupportActionBar(toolbar)
 
-    viewModel.buffer_liveData.observe(this, Observer {
+    viewModel.buffer.toLiveData().observe(this, Observer {
       if (it != null && drawerLayout.isDrawerOpen(Gravity.START)) {
         drawerLayout.closeDrawer(Gravity.START, true)
       }
@@ -151,8 +151,8 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
       }
     }
 
-    viewModel.errors.observe(this, Observer { error ->
-      error?.let {
+    viewModel.errors.toLiveData().observe(this, Observer { error ->
+      error?.orNull()?.let {
         when (it) {
           is HandshakeMessage.ClientInitReject  ->
             MaterialDialog.Builder(this)
@@ -232,7 +232,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
       }
     })
 
-    viewModel.connectionProgress_liveData.observe(this, Observer {
+    viewModel.connectionProgress.toLiveData().observe(this, Observer {
       val (state, progress, max) = it ?: Triple(ConnectionState.DISCONNECTED, 0, 0)
       when (state) {
         ConnectionState.CONNECTED -> {
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt
index cc52089d3702bcd205a59f358b382cf7aa8bc021..63fbb4182388ac65dcd0b74b27fc12a66eb4fd89 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt
@@ -230,7 +230,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     )
     chatList.adapter = listAdapter
 
-    viewModel.selectedBuffer_liveData.observe(this, Observer { buffer ->
+    viewModel.selectedBuffer.toLiveData().observe(this, Observer { buffer ->
       if (buffer != null) {
         val menu = actionMode?.menu
         if (menu != null) {
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt
index 98c1dbe6999916354d61afa5b0467e0b15abb7f7..255e89229bc0da9f5e763613f9743eb0b96eeb12 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt
@@ -19,10 +19,7 @@ import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.settings.AutoCompleteSettings
 import de.kuschku.quasseldroid.settings.MessageSettings
-import de.kuschku.quasseldroid.util.helper.invoke
-import de.kuschku.quasseldroid.util.helper.lineSequence
-import de.kuschku.quasseldroid.util.helper.retint
-import de.kuschku.quasseldroid.util.helper.visibleIf
+import de.kuschku.quasseldroid.util.helper.*
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatSerializer
 import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
@@ -126,9 +123,8 @@ class ChatlineFragment : ServiceBoundFragment() {
       historyPanel.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED
     }
     messageHistory.adapter = messageHistoryAdapter
-    viewModel.recentlySentMessages_liveData.observe(
-      this, Observer(messageHistoryAdapter::submitList)
-    )
+    viewModel.recentlySentMessages.toLiveData()
+      .observe(this, Observer(messageHistoryAdapter::submitList))
 
     fun send() {
       if (chatline.text.isNotBlank()) {
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt
index 28dc3c523da437a956493499d85d6d2437305d53..68bafbb81bb30fccc5789678ad5731def5beda1e 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt
@@ -274,7 +274,7 @@ class MessageListFragment : ServiceBoundFragment() {
         }
       }
 
-    val lastMessageId = viewModel.buffer_liveData.switchMapNotNull {
+    val lastMessageId = viewModel.buffer.toLiveData().switchMapNotNull {
       database.message().lastMsgId(it)
     }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt
deleted file mode 100644
index 60a5af7308f1eb72d7a6ac9a19fd53c1a4b61a8f..0000000000000000000000000000000000000000
--- a/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package de.kuschku.quasseldroid.util
-
-import de.kuschku.libquassel.quassel.syncables.IrcUser
-import de.kuschku.libquassel.util.irc.HostmaskHelper
-import de.kuschku.quasseldroid.persistence.QuasselDatabase
-
-object AvatarHelper {
-  fun avatar(message: QuasselDatabase.DatabaseMessage? = null, user: IrcUser? = null): String? {
-    val ident = message?.sender?.let(HostmaskHelper::user)
-                ?: user?.user()
-                ?: return null
-
-    val userId = Regex("[us]id(\\d+)").matchEntire(ident)?.groupValues?.lastOrNull()
-                 ?: return null
-
-    return "https://static.irccloud-cdn.com/avatar-redirect/$userId"
-  }
-}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/compatibility/AndroidHandlerService.kt b/app/src/main/java/de/kuschku/quasseldroid/util/compatibility/AndroidHandlerService.kt
index b61c3b793ca4fb4a1d0af66a2ce69cd0e9983f02..3da80f703d0f14d896a2239580330c50d760eff8 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/compatibility/AndroidHandlerService.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/compatibility/AndroidHandlerService.kt
@@ -2,10 +2,18 @@ package de.kuschku.quasseldroid.util.compatibility
 
 import android.os.Handler
 import android.os.HandlerThread
+import android.os.Message
 import android.os.Process
 import de.kuschku.libquassel.util.compatibility.HandlerService
+import io.reactivex.Scheduler
+import io.reactivex.disposables.Disposable
+import io.reactivex.disposables.Disposables
+import io.reactivex.plugins.RxJavaPlugins
+import java.util.concurrent.TimeUnit
 
 class AndroidHandlerService : HandlerService {
+  override lateinit var scheduler: Scheduler
+
   override fun serialize(f: () -> Unit) {
     serializeHandler.post(f)
   }
@@ -56,6 +64,8 @@ class AndroidHandlerService : HandlerService {
     deserializeHandler = Handler(deserializeThread.looper)
     writeHandler = Handler(writeThread.looper)
     backendHandler = Handler(backendThread.looper)
+
+    scheduler = HandlerScheduler(backendHandler)
   }
 
   override var exceptionHandler: Thread.UncaughtExceptionHandler? = null
@@ -67,4 +77,77 @@ class AndroidHandlerService : HandlerService {
     writeThread.quit()
     backendThread.quit()
   }
+
+  internal class HandlerScheduler(private val handler: Handler) : Scheduler() {
+    override fun scheduleDirect(run: Runnable, delay: Long, unit: TimeUnit): Disposable {
+      val scheduled = ScheduledRunnable(handler, RxJavaPlugins.onSchedule(run))
+      handler.postDelayed(scheduled, unit.toMillis(delay))
+      return scheduled
+    }
+
+    override fun createWorker(): Scheduler.Worker {
+      return HandlerWorker(handler)
+    }
+
+    private class HandlerWorker constructor(private val handler: Handler) : Scheduler.Worker() {
+      @Volatile
+      private var disposed: Boolean = false
+
+      override fun schedule(run: Runnable, delay: Long, unit: TimeUnit): Disposable {
+        if (disposed) {
+          return Disposables.disposed()
+        }
+
+        val scheduled = ScheduledRunnable(handler, RxJavaPlugins.onSchedule(run))
+
+        val message = Message.obtain(handler, scheduled)
+        message.obj = this // Used as token for batch disposal of this worker's runnables.
+
+        handler.sendMessageDelayed(message, unit.toMillis(delay))
+
+        // Re-check disposed state for removing in case we were racing a call to dispose().
+        if (disposed) {
+          handler.removeCallbacks(scheduled)
+          return Disposables.disposed()
+        }
+
+        return scheduled
+      }
+
+      override fun dispose() {
+        disposed = true
+        handler.removeCallbacksAndMessages(this /* token */)
+      }
+
+      override fun isDisposed(): Boolean {
+        return disposed
+      }
+    }
+
+    private class ScheduledRunnable internal constructor(private val handler: Handler,
+                                                         private val delegate: Runnable) : Runnable,
+                                                                                           Disposable {
+
+      @Volatile
+      private var disposed: Boolean = false
+
+      override fun run() {
+        try {
+          delegate.run()
+        } catch (t: Throwable) {
+          RxJavaPlugins.onError(t)
+        }
+
+      }
+
+      override fun dispose() {
+        disposed = true
+        handler.removeCallbacks(this)
+      }
+
+      override fun isDisposed(): Boolean {
+        return disposed
+      }
+    }
+  }
 }