diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 3ed98b035c39c6bcf19d71cd2c4765251e40454d..7b426b435e23d3b9f5f74803822ac5128f78947b 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -34,7 +34,7 @@ android {
     minSdkVersion(16)
     targetSdkVersion(27)
 
-    applicationId = "de.kuschku.quasseldroid_ng"
+    applicationId = "de.kuschku.quasseldroid"
     versionCode = cmd("git", "rev-list", "--count", "HEAD")?.toIntOrNull() ?: 1
     versionName = cmd("git", "describe", "--always", "--tags", "HEAD") ?: "1.0.0"
 
@@ -83,7 +83,7 @@ android {
 }
 
 dependencies {
-  implementation(kotlin("stdlib", "1.2.30"))
+  implementation(kotlin("stdlib", "1.2.31"))
 
   // App Compat
   withVersion("27.1.0") {
@@ -136,6 +136,7 @@ dependencies {
   // UI
   implementation("me.zhanghai.android.materialprogressbar", "library", "1.4.2")
   implementation("com.afollestad.material-dialogs", "core", "0.9.6.0")
+  implementation("com.stepstone.stepper", "material-stepper", "4.3.1")
   implementation(project(":slidingpanel"))
 
   // Quality Assurance
diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
index 50faea8d0ab34621d8dfb2db6120b88378cbe148..0dbd525c08339ab5bc7b0a02946dc3308ff5f310 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
@@ -69,7 +69,7 @@ class QuasselService : DaggerLifecycleService(),
   private var progress = Triple(ConnectionState.DISCONNECTED, 0, 0)
 
   private fun updateNotificationStatus() {
-    if (connectionSettings?.showNotification == true) {
+    if (connectionSettings.showNotification) {
       val notificationHandle = notificationManager.notificationBackground()
       this.notificationHandle = notificationHandle
       updateNotification(notificationHandle)
@@ -268,12 +268,15 @@ class QuasselService : DaggerLifecycleService(),
       sessionManager.state.filter { it == ConnectionState.DISCONNECTED || it == ConnectionState.CLOSED },
       connectivity,
       BiFunction { a: ConnectionState, b: Unit -> a to b })
+      .distinctUntilChanged()
       .delay(200, TimeUnit.MILLISECONDS)
-      .throttleFirst(1, TimeUnit.SECONDS)
+      .throttleFirst(5, TimeUnit.SECONDS)
       .toLiveData()
       .observe(
         this, Observer {
-        sessionManager.reconnect(true)
+        sessionManager.ifDisconnected {
+          sessionManager.reconnect(true)
+        }
       })
 
     sharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) {
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 c80fc117ce4e012fb2f7f874b3aa819e49c5dde0..b8d174a5f87d1097adecf61970bf2751409a5eb6 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
@@ -38,6 +38,7 @@ import de.kuschku.quasseldroid.ui.chat.input.Editor
 import de.kuschku.quasseldroid.ui.chat.input.MessageHistoryAdapter
 import de.kuschku.quasseldroid.ui.settings.SettingsActivity
 import de.kuschku.quasseldroid.util.helper.invoke
+import de.kuschku.quasseldroid.util.helper.toLiveData
 import de.kuschku.quasseldroid.util.service.ServiceBoundActivity
 import de.kuschku.quasseldroid.util.ui.MaterialContentLoadingProgressBar
 import de.kuschku.quasseldroid.viewmodel.QuasselViewModel
@@ -100,11 +101,11 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
     ButterKnife.bind(this)
 
     viewModel = ViewModelProviders.of(this)[QuasselViewModel::class.java]
-    viewModel.setBackend(this.backend)
+    viewModel.backendWrapper.onNext(this.backend)
 
     editor = Editor(
       this,
-      viewModel.autoCompleteData,
+      viewModel.autoCompleteData.toLiveData(),
       viewModel.lastWord,
       findViewById(R.id.chatline),
       findViewById(R.id.send),
@@ -146,11 +147,12 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
       historyPanel.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED
     }
     msgHistory.adapter = messageHistoryAdapter
-    viewModel.recentlySentMessages.observe(this, Observer(messageHistoryAdapter::submitList))
+    viewModel.recentlySentMessages_liveData.observe(this,
+                                                    Observer(messageHistoryAdapter::submitList))
 
     setSupportActionBar(toolbar)
 
-    viewModel.buffer.observe(this, Observer {
+    viewModel.buffer_liveData.observe(this, Observer {
       if (it != null && drawerLayout.isDrawerOpen(Gravity.START)) {
         drawerLayout.closeDrawer(Gravity.START, true)
       }
@@ -168,7 +170,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
       drawerToggle.syncState()
     }
 
-    viewModel.errors.observe(this, Observer {
+    viewModel.errors_liveData.observe(this, Observer {
       when (it) {
         is HandshakeMessage.ClientInitReject  ->
           MaterialDialog.Builder(this)
@@ -194,7 +196,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
       }
     })
 
-    viewModel.connectionProgress.observe(this, Observer { it ->
+    viewModel.connectionProgress_liveData.observe(this, Observer { it ->
       val (state, progress, max) = it ?: Triple(ConnectionState.DISCONNECTED, 0, 0)
       when (state) {
         ConnectionState.CONNECTED,
@@ -239,7 +241,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
 
   override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
     super.onRestoreInstanceState(savedInstanceState)
-    viewModel.buffer.value = savedInstanceState?.getInt("OPEN_BUFFER", -1) ?: -1
+    viewModel.buffer.onNext(savedInstanceState?.getInt("OPEN_BUFFER", -1) ?: -1)
   }
 
   @TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -248,7 +250,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
     super.onRestoreInstanceState(savedInstanceState, persistentState)
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
       val fallback = persistentState?.getInt("OPEN_BUFFER", -1) ?: -1
-      viewModel.buffer.value = savedInstanceState?.getInt("OPEN_BUFFER", fallback) ?: fallback
+      viewModel.buffer.onNext(savedInstanceState?.getInt("OPEN_BUFFER", fallback) ?: fallback)
     }
   }
 
@@ -263,47 +265,49 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
     }
 
     R.id.filter_messages -> {
-      viewModel.buffer { buffer ->
-        val filtered = Message_Type.of(database.filtered().get(accountId, buffer) ?: 0)
-        val flags = intArrayOf(
-          Message.MessageType.Join.bit or Message.MessageType.NetsplitJoin.bit,
-          Message.MessageType.Part.bit,
-          Message.MessageType.Quit.bit or Message.MessageType.NetsplitQuit.bit,
-          Message.MessageType.Nick.bit,
-          Message.MessageType.Mode.bit,
-          Message.MessageType.Topic.bit
-        )
-        val selectedIndices = flags.withIndex().mapNotNull { (index, flag) ->
-          if ((filtered and flag).isNotEmpty()) {
-            index
-          } else {
-            null
-          }
-        }.toTypedArray()
-
-        MaterialDialog.Builder(this)
-          .title(R.string.label_filter_messages)
-          .items(R.array.message_filter_types)
-          .itemsIds(flags)
-          .itemsCallbackMultiChoice(selectedIndices, { _, _, _ -> false })
-          .positiveText(R.string.label_select_multiple)
-          .negativeText(R.string.label_cancel)
-          .onPositive { dialog, _ ->
-            val selected = dialog.selectedIndices ?: emptyArray()
-            runInBackground {
-              val newlyFiltered = selected
-                .map { flags[it] }
-                .fold(Message_Type.of()) { acc, i -> acc or i }
-
-              database.filtered().replace(
-                QuasselDatabase.Filtered(accountId, buffer, newlyFiltered.value)
-              )
+      runInBackground {
+        viewModel.buffer { buffer ->
+          val filtered = Message_Type.of(database.filtered().get(accountId, buffer) ?: 0)
+          val flags = intArrayOf(
+            Message.MessageType.Join.bit or Message.MessageType.NetsplitJoin.bit,
+            Message.MessageType.Part.bit,
+            Message.MessageType.Quit.bit or Message.MessageType.NetsplitQuit.bit,
+            Message.MessageType.Nick.bit,
+            Message.MessageType.Mode.bit,
+            Message.MessageType.Topic.bit
+          )
+          val selectedIndices = flags.withIndex().mapNotNull { (index, flag) ->
+            if ((filtered and flag).isNotEmpty()) {
+              index
+            } else {
+              null
             }
-          }.negativeColorAttr(R.attr.colorTextPrimary)
-          .backgroundColorAttr(R.attr.colorBackgroundCard)
-          .contentColorAttr(R.attr.colorTextPrimary)
-          .build()
-          .show()
+          }.toTypedArray()
+
+          MaterialDialog.Builder(this)
+            .title(R.string.label_filter_messages)
+            .items(R.array.message_filter_types)
+            .itemsIds(flags)
+            .itemsCallbackMultiChoice(selectedIndices, { _, _, _ -> false })
+            .positiveText(R.string.label_select_multiple)
+            .negativeText(R.string.label_cancel)
+            .onPositive { dialog, _ ->
+              val selected = dialog.selectedIndices ?: emptyArray()
+              runInBackground {
+                val newlyFiltered = selected
+                  .map { flags[it] }
+                  .fold(Message_Type.of()) { acc, i -> acc or i }
+
+                database.filtered().replace(
+                  QuasselDatabase.Filtered(accountId, buffer, newlyFiltered.value)
+                )
+              }
+            }.negativeColorAttr(R.attr.colorTextPrimary)
+            .backgroundColorAttr(R.attr.colorBackgroundCard)
+            .contentColorAttr(R.attr.colorTextPrimary)
+            .build()
+            .show()
+        }
       }
       true
     }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ToolbarFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ToolbarFragment.kt
index e6961292f39af686181c242df05e71d2755aa425..a54b42ca7f10f649df11cb9ed49b6f621d920930 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ToolbarFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ToolbarFragment.kt
@@ -14,8 +14,9 @@ import de.kuschku.libquassel.protocol.Buffer_Type
 import de.kuschku.libquassel.util.hasFlag
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.settings.AppearanceSettings
+import de.kuschku.quasseldroid.util.helper.combineLatest
+import de.kuschku.quasseldroid.util.helper.toLiveData
 import de.kuschku.quasseldroid.util.helper.visibleIf
-import de.kuschku.quasseldroid.util.helper.zip
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
 import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
 import de.kuschku.quasseldroid.util.ui.SpanFormatter
@@ -58,38 +59,41 @@ class ToolbarFragment : ServiceBoundFragment() {
     viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java]
   }
 
-  override fun onCreateView(inflater: LayoutInflater,
-                            container: ViewGroup?,
-                            savedInstanceState: Bundle?): View? {
+  override fun onCreateView(
+    inflater: LayoutInflater,
+    container: ViewGroup?,
+    savedInstanceState: Bundle?
+  ): View? {
     val view = inflater.inflate(R.layout.fragment_toolbar, container, false)
     ButterKnife.bind(this, view)
 
-    viewModel.bufferData.zip(viewModel.isSecure, viewModel.lag).observe(
-      this, Observer {
-      if (it != null) {
-        val (data, isSecure, lag) = it
-        if (data?.info?.type?.hasFlag(Buffer_Type.StatusBuffer) == true) {
-          this.title = data.network?.networkName
-        } else {
-          this.title = data?.info?.bufferName
-        }
+    combineLatest(viewModel.bufferData, viewModel.isSecure, viewModel.lag).toLiveData()
+      .observe(this, Observer {
+        if (it != null) {
+          val (data, isSecure, lag) = it
+          if (data?.info?.type?.hasFlag(Buffer_Type.StatusBuffer) == true) {
+            this.title = data.network?.networkName
+          } else {
+            this.title = data?.info?.bufferName
+          }
 
-        if (lag == 0L || !appearanceSettings.showLag) {
-          this.subtitle = colorizeDescription(data?.description)
-        } else {
-          val description = colorizeDescription(data?.description)
-          if (description.isNullOrBlank()) {
-            this.subtitle = "Lag: ${lag}ms"
+          if (lag == 0L || !appearanceSettings.showLag) {
+            this.subtitle = colorizeDescription(data?.description)
           } else {
-            this.subtitle = SpanFormatter.format(
-              "Lag: %dms | %s",
-              lag,
-              colorizeDescription(data?.description)
-            )
+            val description = colorizeDescription(data?.description)
+            if (description.isNullOrBlank()) {
+              this.subtitle = "Lag: ${lag}ms"
+            } else {
+              this.subtitle = SpanFormatter.format(
+                "Lag: %dms | %s",
+                lag,
+                colorizeDescription(data?.description)
+              )
+            }
           }
         }
       }
-    })
+      )
 
     return view
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt
index 190c4ac470be3dccfc96702e20c69a90314909b3..e720a15720dd592c6197703f1287d622aa4f8424 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt
@@ -2,7 +2,6 @@ package de.kuschku.quasseldroid.ui.chat.buffers
 
 import android.arch.lifecycle.LifecycleOwner
 import android.arch.lifecycle.LiveData
-import android.arch.lifecycle.MutableLiveData
 import android.arch.lifecycle.Observer
 import android.graphics.drawable.Drawable
 import android.support.v4.graphics.drawable.DrawableCompat
@@ -22,20 +21,18 @@ import de.kuschku.libquassel.protocol.NetworkId
 import de.kuschku.libquassel.quassel.BufferInfo
 import de.kuschku.libquassel.util.hasFlag
 import de.kuschku.quasseldroid.R
-import de.kuschku.quasseldroid.util.helper.getCompatDrawable
-import de.kuschku.quasseldroid.util.helper.styledAttributes
-import de.kuschku.quasseldroid.util.helper.visibleIf
-import de.kuschku.quasseldroid.util.helper.zip
+import de.kuschku.quasseldroid.util.helper.*
 import de.kuschku.quasseldroid.viewmodel.data.BufferListItem
 import de.kuschku.quasseldroid.viewmodel.data.BufferProps
 import de.kuschku.quasseldroid.viewmodel.data.BufferState
 import de.kuschku.quasseldroid.viewmodel.data.BufferStatus
+import io.reactivex.subjects.BehaviorSubject
 
 class BufferListAdapter(
   lifecycleOwner: LifecycleOwner,
   liveData: LiveData<List<BufferProps>?>,
-  private val selectedBuffer: MutableLiveData<BufferId>,
-  private val collapsedNetworks: MutableLiveData<Set<NetworkId>>,
+  private val selectedBuffer: BehaviorSubject<BufferId>,
+  private val collapsedNetworks: BehaviorSubject<Set<NetworkId>>,
   runInBackground: (() -> Unit) -> Any,
   runOnUiThread: (Runnable) -> Any,
   private val clickListener: ((BufferId) -> Unit)? = null,
@@ -45,25 +42,25 @@ class BufferListAdapter(
 
   fun expandListener(networkId: NetworkId) {
     if (collapsedNetworks.value.orEmpty().contains(networkId))
-      collapsedNetworks.postValue(collapsedNetworks.value.orEmpty() - networkId)
+      collapsedNetworks.onNext(collapsedNetworks.value.orEmpty() - networkId)
     else
-      collapsedNetworks.postValue(collapsedNetworks.value.orEmpty() + networkId)
+      collapsedNetworks.onNext(collapsedNetworks.value.orEmpty() + networkId)
   }
 
   fun toggleSelection(buffer: BufferId) {
     if (selectedBuffer.value == buffer) {
-      selectedBuffer.value = -1
+      selectedBuffer.onNext(-1)
     } else {
-      selectedBuffer.value = buffer
+      selectedBuffer.onNext(buffer)
     }
   }
 
   fun unselectAll() {
-    selectedBuffer.value = -1
+    selectedBuffer.onNext(-1)
   }
 
   init {
-    liveData.zip(collapsedNetworks, selectedBuffer).observe(
+    liveData.zip(collapsedNetworks.toLiveData(), selectedBuffer.toLiveData()).observe(
       lifecycleOwner, Observer { it: Triple<List<BufferProps>?, Set<NetworkId>, BufferId>? ->
       runInBackground {
         val list = it?.first ?: emptyList()
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 97338e7ae25f1450eb24985ba10cb3acc05e3bdd..714e484ade6dad537465ee9b682c1e530271cc27 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
@@ -19,11 +19,13 @@ import de.kuschku.libquassel.protocol.Buffer_Type
 import de.kuschku.libquassel.protocol.Message_Type
 import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
 import de.kuschku.libquassel.util.hasFlag
+import de.kuschku.libquassel.util.helpers.value
 import de.kuschku.libquassel.util.minus
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.util.helper.map
+import de.kuschku.quasseldroid.util.helper.toLiveData
 import de.kuschku.quasseldroid.util.helper.zip
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
 import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
@@ -126,16 +128,16 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
           }
           R.id.action_unhide     -> {
             bufferSyncer?.let {
-              bufferViewConfig?.requestAddBuffer(info, bufferSyncer)
+              bufferViewConfig?.orNull()?.requestAddBuffer(info, bufferSyncer)
             }
             true
           }
           R.id.action_hide_temp  -> {
-            bufferViewConfig?.requestRemoveBuffer(info.bufferId)
+            bufferViewConfig?.orNull()?.requestRemoveBuffer(info.bufferId)
             true
           }
           R.id.action_hide_perm  -> {
-            bufferViewConfig?.requestRemoveBufferPermanently(info.bufferId)
+            bufferViewConfig?.orNull()?.requestRemoveBufferPermanently(info.bufferId)
             true
           }
           else                   -> false
@@ -168,27 +170,29 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java]
   }
 
-  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
-                            savedInstanceState: Bundle?): View? {
+  override fun onCreateView(
+    inflater: LayoutInflater, container: ViewGroup?,
+    savedInstanceState: Bundle?
+  ): View? {
     val view = inflater.inflate(R.layout.fragment_chat_list, container, false)
     ButterKnife.bind(this, view)
 
-    val adapter = BufferViewConfigAdapter(this, viewModel.bufferViewConfigs)
+    val adapter = BufferViewConfigAdapter(this, viewModel.bufferViewConfigs.toLiveData())
 
     chatListSpinner.adapter = adapter
     chatListSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
       override fun onNothingSelected(p0: AdapterView<*>?) {
-        viewModel.setBufferViewConfigId(null)
+        viewModel.bufferViewConfigId.onNext(-1)
       }
 
       override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
-        viewModel.setBufferViewConfigId(adapter.getItem(p2)?.bufferViewId())
+        viewModel.bufferViewConfigId.onNext(adapter.getItem(p2)?.bufferViewId() ?: -1)
       }
     }
 
     listAdapter = BufferListAdapter(
       this,
-      viewModel.bufferList.zip(database.filtered().listen(accountId)).map {
+      viewModel.bufferList.toLiveData().zip(database.filtered().listen(accountId)).map {
         val (data, activityList) = it
         val (config, list) = data ?: Pair(null, emptyList())
         val minimumActivity = config?.minimumActivity() ?: Buffer_Activity.NONE
@@ -225,7 +229,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     )
     chatList.adapter = listAdapter
 
-    viewModel.selectedBuffer.observe(this, Observer { buffer ->
+    viewModel.selectedBuffer_liveData.observe(this, Observer { buffer ->
       if (buffer != null) {
         val menu = actionMode?.menu
         if (menu != null) {
@@ -298,7 +302,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
       when (item.itemId) {
         R.id.action_show_hidden -> {
           item.isChecked = !item.isChecked
-          viewModel.showHidden.value = item.isChecked
+          viewModel.showHidden.onNext(item.isChecked)
           true
         }
         else                    -> false
@@ -314,7 +318,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     if (actionMode != null) {
       longClickListener?.invoke(it)
     } else {
-      viewModel.buffer.value = it
+      viewModel.buffer.onNext(it)
     }
   }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/Editor.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/Editor.kt
index 61987afdefc1ca0b417005c0538264035ed22023..ab3ceea63e448fd933f653552b1961f1d1aad7e5 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/Editor.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/Editor.kt
@@ -1,7 +1,6 @@
 package de.kuschku.quasseldroid.ui.chat.input
 
 import android.arch.lifecycle.LiveData
-import android.arch.lifecycle.MutableLiveData
 import android.arch.lifecycle.Observer
 import android.support.v7.app.AppCompatActivity
 import android.support.v7.widget.*
@@ -25,8 +24,8 @@ class Editor(
   // Contexts
   activity: AppCompatActivity,
   // LiveData
-  private val autoCompleteData: LiveData<Pair<String, List<AutoCompleteItem>>?>,
-  lastWordContainer: MutableLiveData<Observable<Pair<String, IntRange>>>,
+  private val autoCompleteData: LiveData<Pair<String, List<AutoCompleteItem>>>,
+  lastWordContainer: BehaviorSubject<Observable<Pair<String, IntRange>>>,
   // Views
   val chatline: AppCompatEditText,
   send: AppCompatImageButton,
@@ -138,7 +137,7 @@ class Editor(
       }
     }
 
-    lastWordContainer.value = lastWord
+    lastWordContainer.onNext(lastWord)
 
     activity.menuInflater.inflate(formatHandler.menu, formattingMenu.menu)
     formattingMenu.menu.retint(activity)
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 2be7de09c64e223b837b0b0b61e2bae2c4fbea8f..47fe8b8075bbe9df07cfa75b888a835d50dc5dd8 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
@@ -16,6 +16,7 @@ import butterknife.ButterKnife
 import de.kuschku.libquassel.protocol.BufferId
 import de.kuschku.libquassel.protocol.MsgId
 import de.kuschku.libquassel.quassel.syncables.BufferSyncer
+import de.kuschku.libquassel.util.helpers.value
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.settings.AppearanceSettings
@@ -66,8 +67,10 @@ class MessageListFragment : ServiceBoundFragment() {
     override fun onItemAtEndLoaded(itemAtEnd: QuasselDatabase.DatabaseMessage) = loadMore()
   }
 
-  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
-                            savedInstanceState: Bundle?): View? {
+  override fun onCreateView(
+    inflater: LayoutInflater, container: ViewGroup?,
+    savedInstanceState: Bundle?
+  ): View? {
     val view = inflater.inflate(R.layout.fragment_messages, container, false)
     ButterKnife.bind(this, view)
 
@@ -90,7 +93,7 @@ class MessageListFragment : ServiceBoundFragment() {
         }
       })
 
-    val data = viewModel.buffer.switchMapNotNull { buffer ->
+    val data = viewModel.buffer_liveData.switchMapNotNull { buffer ->
       database.filtered().listen(accountId, buffer).switchMapNotNull { filtered ->
         LivePagedListBuilder(
           database.message().findByBufferIdPaged(buffer, filtered),
@@ -104,11 +107,11 @@ class MessageListFragment : ServiceBoundFragment() {
       }
     }
 
-    val lastMessageId = viewModel.buffer.switchMapNotNull {
+    val lastMessageId = viewModel.buffer_liveData.switchMapNotNull {
       database.message().lastMsgId(it)
     }
 
-    viewModel.sessionManager.zip(lastMessageId).observe(
+    viewModel.sessionManager_liveData.zip(lastMessageId).observe(
       this, Observer {
       runInBackground {
         val session = it?.first
@@ -121,7 +124,7 @@ class MessageListFragment : ServiceBoundFragment() {
       }
     })
 
-    viewModel.markerLine.observe(this, Observer {
+    viewModel.markerLine_liveData.observe(this, Observer {
       adapter.markerLinePosition = it
       adapter.notifyDataSetChanged()
     })
@@ -131,15 +134,8 @@ class MessageListFragment : ServiceBoundFragment() {
       val firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition()
       val firstVisibleMessageId = adapter[firstVisibleItemPosition]?.messageId
       runInBackground {
-        val buffer = viewModel.buffer.value ?: -1
-        if (buffer != lastBuffer) {
-          backend.value?.sessionManager()?.bufferSyncer?.let { bufferSyncer ->
-            onBufferChange(lastBuffer, buffer, firstVisibleMessageId, bufferSyncer)
-          }
-          lastBuffer = buffer
-          adapter.clearCache()
-        }
         adapter.submitList(list)
+
         if (firstVisibleItemPosition < 2) {
           activity?.runOnUiThread { messageList.scrollToPosition(0) }
           runInBackgroundDelayed(16) {
@@ -148,6 +144,15 @@ class MessageListFragment : ServiceBoundFragment() {
             }
           }
         }
+
+        val buffer = viewModel.buffer.value ?: -1
+        if (buffer != lastBuffer) {
+          backend.value.orNull()?.sessionManager()?.bufferSyncer?.let { bufferSyncer ->
+            onBufferChange(lastBuffer, buffer, firstVisibleMessageId, bufferSyncer)
+          }
+          lastBuffer = buffer
+          adapter.clearCache()
+        }
       }
     })
     scrollDown.hide()
@@ -161,8 +166,10 @@ class MessageListFragment : ServiceBoundFragment() {
       bufferSyncer.requestSetLastSeenMsg(buffer, lastMessageId)
   }
 
-  private fun onBufferChange(previous: BufferId?, current: BufferId, lastMessageId: MsgId?,
-                             bufferSyncer: BufferSyncer) {
+  private fun onBufferChange(
+    previous: BufferId?, current: BufferId, lastMessageId: MsgId?,
+    bufferSyncer: BufferSyncer
+  ) {
     if (previous != null && lastMessageId != null) {
       bufferSyncer.requestSetMarkerLine(previous, lastMessageId)
     }
@@ -187,13 +194,15 @@ class MessageListFragment : ServiceBoundFragment() {
   private fun loadMore() {
     runInBackground {
       viewModel.buffer { bufferId ->
-        viewModel.sessionManager()?.backlogManager?.requestBacklog(
-          bufferId = bufferId,
-          last = database.message().findFirstByBufferId(
-            bufferId
-          )?.messageId ?: -1,
-          limit = backlogSettings.dynamicAmount
-        )
+        viewModel.sessionManager {
+          it.backlogManager?.requestBacklog(
+            bufferId = bufferId,
+            last = database.message().findFirstByBufferId(
+              bufferId
+            )?.messageId ?: -1,
+            limit = backlogSettings.dynamicAmount
+          )
+        }
       }
     }
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt
index a429a77a2083a1288f1a7bd2a71de2e9c2db04fa..242998f2776a4d693259de7edd2d88b8ebb0be8e 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt
@@ -14,7 +14,7 @@ import butterknife.ButterKnife
 import de.kuschku.libquassel.util.irc.IrcCaseMappers
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.settings.AppearanceSettings
-import de.kuschku.quasseldroid.util.helper.map
+import de.kuschku.quasseldroid.util.helper.toLiveData
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
 import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
 import de.kuschku.quasseldroid.viewmodel.QuasselViewModel
@@ -65,7 +65,7 @@ class NickListFragment : ServiceBoundFragment() {
       }.sortedBy {
         it.lowestMode
       }
-    }.observe(this, Observer(nickListAdapter::submitList))
+    }.toLiveData().observe(this, Observer(nickListAdapter::submitList))
 
     return view
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/service/BackendServiceConnection.kt b/app/src/main/java/de/kuschku/quasseldroid/util/service/BackendServiceConnection.kt
index 45f4f2f5911afd535504ae14b5c7ccff0ef1d34e..35a72d7c63337a8855a86dc80c19dfaac96be099 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/service/BackendServiceConnection.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/service/BackendServiceConnection.kt
@@ -1,23 +1,24 @@
 package de.kuschku.quasseldroid.util.service
 
-import android.arch.lifecycle.MutableLiveData
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
 import android.content.ServiceConnection
 import android.os.IBinder
 import de.kuschku.libquassel.session.Backend
+import de.kuschku.libquassel.util.Optional
 import de.kuschku.quasseldroid.service.QuasselService
+import io.reactivex.subjects.BehaviorSubject
 
 class BackendServiceConnection : ServiceConnection {
-  val backend = MutableLiveData<Backend?>()
+  val backend = BehaviorSubject.createDefault(Optional.empty<Backend>())
 
   var context: Context? = null
 
   override fun onServiceDisconnected(component: ComponentName?) {
     when (component) {
       ComponentName(context, QuasselService::class.java) -> {
-        backend.value = null
+        backend.onNext(Optional.empty())
       }
     }
   }
@@ -26,7 +27,7 @@ class BackendServiceConnection : ServiceConnection {
     when (component) {
       ComponentName(context, QuasselService::class.java) ->
         if (binder is QuasselService.QuasselBinder) {
-          backend.value = binder.backend
+          backend.onNext(Optional.of(binder.backend))
         }
     }
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundActivity.kt
index 0d6757ba6d61e7f371216a32b2f9a6ff781d2752..d5e987d835c28e0a78fae283cb9f494d403c216c 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundActivity.kt
@@ -1,7 +1,6 @@
 package de.kuschku.quasseldroid.util.service
 
 import android.app.Activity
-import android.arch.lifecycle.LiveData
 import android.content.Context
 import android.content.Intent
 import android.content.SharedPreferences
@@ -16,6 +15,7 @@ import dagger.android.DispatchingAndroidInjector
 import dagger.android.HasFragmentInjector
 import dagger.android.support.HasSupportFragmentInjector
 import de.kuschku.libquassel.session.Backend
+import de.kuschku.libquassel.util.Optional
 import de.kuschku.libquassel.util.compatibility.LoggingHandler
 import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log
 import de.kuschku.quasseldroid.Keys
@@ -24,9 +24,9 @@ import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.settings.ConnectionSettings
 import de.kuschku.quasseldroid.settings.Settings
 import de.kuschku.quasseldroid.ui.setup.accounts.selection.AccountSelectionActivity
-import de.kuschku.quasseldroid.util.helper.invoke
 import de.kuschku.quasseldroid.util.helper.sharedPreferences
 import de.kuschku.quasseldroid.util.helper.updateRecentsHeaderIfExisting
+import io.reactivex.subjects.BehaviorSubject
 import javax.inject.Inject
 
 abstract class ServiceBoundActivity : AppCompatActivity(),
@@ -39,7 +39,7 @@ abstract class ServiceBoundActivity : AppCompatActivity(),
   protected val recentsHeaderColor: Int = R.color.colorPrimary
 
   private val connection = BackendServiceConnection()
-  protected val backend: LiveData<Backend?>
+  protected val backend: BehaviorSubject<Optional<Backend>>
     get() = connection.backend
 
 
@@ -58,13 +58,13 @@ abstract class ServiceBoundActivity : AppCompatActivity(),
   }
 
   protected fun runInBackground(f: () -> Unit) {
-    connection.backend {
+    connection.backend.value.ifPresent {
       it.sessionManager().handlerService.backend(f)
     }
   }
 
   protected fun runInBackgroundDelayed(delayMillis: Long, f: () -> Unit) {
-    connection.backend {
+    connection.backend.value.ifPresent {
       it.sessionManager().handlerService.backendDelayed(delayMillis, f)
     }
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundFragment.kt
index e2e582f8d147886f9b54740de9c4aeebbadad205..bb01640e58ec3723d8a4c476edb291692ea49ca5 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundFragment.kt
@@ -1,27 +1,26 @@
 package de.kuschku.quasseldroid.util.service
 
-import android.arch.lifecycle.LiveData
 import android.content.Context
 import android.os.Bundle
 import dagger.android.support.DaggerFragment
 import de.kuschku.libquassel.session.Backend
+import de.kuschku.libquassel.util.Optional
 import de.kuschku.quasseldroid.Keys
-import de.kuschku.quasseldroid.util.helper.invoke
+import io.reactivex.subjects.BehaviorSubject
 
 abstract class ServiceBoundFragment : DaggerFragment() {
-  private var connection = BackendServiceConnection()
-
-  protected val backend: LiveData<Backend?>
+  private val connection = BackendServiceConnection()
+  protected val backend: BehaviorSubject<Optional<Backend>>
     get() = connection.backend
 
   protected fun runInBackground(f: () -> Unit) {
-    connection.backend {
+    connection.backend.value.ifPresent {
       it.sessionManager().handlerService.backend(f)
     }
   }
 
   protected fun runInBackgroundDelayed(delayMillis: Long, f: () -> Unit) {
-    connection.backend {
+    connection.backend.value.ifPresent {
       it.sessionManager().handlerService.backendDelayed(delayMillis, f)
     }
   }
diff --git a/app/src/main/res/layout/widget_chatmessage_action.xml b/app/src/main/res/layout/widget_chatmessage_action.xml
index 43dc3ce6c0c3bda44dba857ed6ac535b2bc9def2..1270a6d77986a435546914430be3a4f3d67dad75 100644
--- a/app/src/main/res/layout/widget_chatmessage_action.xml
+++ b/app/src/main/res/layout/widget_chatmessage_action.xml
@@ -1,30 +1,8 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ QuasselDroid - Quassel client for Android
-  ~ Copyright (C) 2016 Janne Koschinski
-  ~ Copyright (C) 2016 Ken Børge Viktil
-  ~ Copyright (C) 2016 Magnus Fjell
-  ~ Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org>
-  ~
-  ~ This program is free software: you can redistribute it and/or modify it
-  ~ under the terms of the GNU General Public License as published by the Free
-  ~ Software Foundation, either version 3 of the License, or (at your option)
-  ~ any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License along
-  ~ with this program.  If not, see <http://www.gnu.org/licenses/>.
-  -->
-
+<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
-  android:clickable="true"
-  android:focusable="true"
   android:orientation="vertical"
   android:textAppearance="?android:attr/textAppearanceListItemSmall">
 
diff --git a/app/src/main/res/layout/widget_chatmessage_error.xml b/app/src/main/res/layout/widget_chatmessage_error.xml
index 9b8eac026f9133171996190ef6e38b707fe341c5..cbcd73b12de21b16cdb4954b5db780a72c0c1931 100644
--- a/app/src/main/res/layout/widget_chatmessage_error.xml
+++ b/app/src/main/res/layout/widget_chatmessage_error.xml
@@ -3,8 +3,6 @@
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
-  android:clickable="true"
-  android:focusable="true"
   android:orientation="vertical"
   android:textAppearance="?android:attr/textAppearanceListItemSmall">
 
diff --git a/app/src/main/res/layout/widget_chatmessage_info.xml b/app/src/main/res/layout/widget_chatmessage_info.xml
index 0748dd14c9a74f76a7f7ed3f44919772606f04b6..ba643efbbe7f4574df29c72f349322ba529e2e10 100644
--- a/app/src/main/res/layout/widget_chatmessage_info.xml
+++ b/app/src/main/res/layout/widget_chatmessage_info.xml
@@ -3,8 +3,6 @@
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
-  android:clickable="true"
-  android:focusable="true"
   android:orientation="vertical"
   android:textAppearance="?android:attr/textAppearanceListItemSmall">
 
diff --git a/app/src/main/res/layout/widget_chatmessage_notice.xml b/app/src/main/res/layout/widget_chatmessage_notice.xml
index facbf98b298215e434b981ce39342d80292e5349..6bcf8f7cbac80d5a68bda2e0104d5ba4e09d5d2b 100644
--- a/app/src/main/res/layout/widget_chatmessage_notice.xml
+++ b/app/src/main/res/layout/widget_chatmessage_notice.xml
@@ -3,8 +3,6 @@
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
-  android:clickable="true"
-  android:focusable="true"
   android:orientation="vertical"
   android:textAppearance="?android:attr/textAppearanceListItemSmall">
 
diff --git a/app/src/main/res/layout/widget_chatmessage_plain.xml b/app/src/main/res/layout/widget_chatmessage_plain.xml
index 6828a4424a1e38d8324d098652e02176a5ec8061..476865332c167682107583f55db2fd4f7e7ae74a 100644
--- a/app/src/main/res/layout/widget_chatmessage_plain.xml
+++ b/app/src/main/res/layout/widget_chatmessage_plain.xml
@@ -3,8 +3,6 @@
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
-  android:clickable="true"
-  android:focusable="true"
   android:orientation="vertical"
   android:textAppearance="?android:attr/textAppearanceListItemSmall">
 
diff --git a/app/src/main/res/layout/widget_chatmessage_server.xml b/app/src/main/res/layout/widget_chatmessage_server.xml
index fef073da00f2d51f19fe48c33e458907707cc789..00003711c80e2ea2d9ebcb280b9d0a7134959931 100644
--- a/app/src/main/res/layout/widget_chatmessage_server.xml
+++ b/app/src/main/res/layout/widget_chatmessage_server.xml
@@ -3,8 +3,6 @@
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
-  android:clickable="true"
-  android:focusable="true"
   android:orientation="vertical"
   android:textAppearance="?android:attr/textAppearanceListItemSmall">
 
diff --git a/build.gradle.kts b/build.gradle.kts
index 8a0c5cb83d91d54564192987bece64b4ca07befe..4976af8e5165407aa2d7cc5d685f318f077b626d 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -5,7 +5,7 @@ buildscript {
   }
   dependencies {
     classpath("com.android.tools.build:gradle:3.0.1")
-    withVersion("1.2.30") {
+    withVersion("1.2.31") {
       classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$version")
       classpath("org.jetbrains.kotlin:kotlin-android-extensions:$version")
     }
diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts
index 8c2b7c1a60beafb58b56e0b60dc5f09af07ecb04..53138ef030003af6f550d44f322607d07d42c4f7 100644
--- a/lib/build.gradle.kts
+++ b/lib/build.gradle.kts
@@ -10,7 +10,7 @@ plugins {
 }
 
 dependencies {
-  implementation(kotlin("stdlib", "1.2.30"))
+  implementation(kotlin("stdlib", "1.2.31"))
 
   withVersion("27.1.0") {
     implementation("com.android.support", "support-annotations", version)
diff --git a/lib/src/main/java/de/kuschku/libquassel/protocol/primitive/serializer/StringSerializer.kt b/lib/src/main/java/de/kuschku/libquassel/protocol/primitive/serializer/StringSerializer.kt
index 9a2e22becec211391bc756f51a506ac4b5666d79..58e46597c600b4ac1b57ffc5b12096540df14f99 100644
--- a/lib/src/main/java/de/kuschku/libquassel/protocol/primitive/serializer/StringSerializer.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/protocol/primitive/serializer/StringSerializer.kt
@@ -8,7 +8,7 @@ import java.nio.CharBuffer
 import java.nio.charset.Charset
 import java.nio.charset.CharsetDecoder
 import java.nio.charset.CharsetEncoder
-import java.util.concurrent.atomic.AtomicReference
+import kotlin.concurrent.getOrSet
 
 abstract class StringSerializer(
   private val encoder: CharsetEncoder,
@@ -26,14 +26,17 @@ abstract class StringSerializer(
     }
   )
 
-  private val thread = AtomicReference<Thread>()
-  private val charBuffer = CharBuffer.allocate(1024)
+  private val charBuffer = ThreadLocal<CharBuffer>()
 
   object UTF16 : StringSerializer(Charsets.UTF_16BE)
   object UTF8 : StringSerializer(Charsets.UTF_8)
   object C : StringSerializer(Charsets.ISO_8859_1, trailingNullByte = true)
 
   private inline fun charBuffer(len: Int): CharBuffer {
+    val charBuffer = charBuffer.getOrSet {
+      CharBuffer.allocate(1024)
+    }
+
     val buf = if (len >= 1024)
       CharBuffer.allocate(len)
     else
@@ -43,96 +46,75 @@ abstract class StringSerializer(
     return buf
   }
 
-  private inline fun <T> preventThreadRaces(f: () -> T): T {
-    val currentThread = Thread.currentThread()
-    if (!thread.compareAndSet(null, currentThread)) {
-      throw RuntimeException("Illegal Thread access!")
-    }
-    val result: T = f()
-    if (!thread.compareAndSet(currentThread, null)) {
-      throw RuntimeException("Illegal Thread access!")
-    }
-    return result
-  }
-
   override fun serialize(buffer: ChainedByteBuffer, data: String?, features: Quassel_Features) =
-    preventThreadRaces {
-      try {
-        if (data == null) {
-          IntSerializer.serialize(buffer, -1, features)
-        } else {
-          val charBuffer = charBuffer(data.length)
-          charBuffer.put(data)
-          charBuffer.flip()
-          encoder.reset()
-          val byteBuffer = encoder.encode(charBuffer)
-          IntSerializer.serialize(buffer, byteBuffer.remaining() + trailingNullBytes, features)
-          buffer.put(byteBuffer)
-          for (i in 0 until trailingNullBytes)
-            buffer.put(0)
-        }
-      } catch (e: Throwable) {
-        throw RuntimeException(data, e)
-      }
-    }
-
-  fun serialize(data: String?): ByteBuffer = preventThreadRaces {
     try {
       if (data == null) {
-        ByteBuffer.allocate(0)
+        IntSerializer.serialize(buffer, -1, features)
       } else {
         val charBuffer = charBuffer(data.length)
         charBuffer.put(data)
         charBuffer.flip()
         encoder.reset()
-        encoder.encode(charBuffer)
+        val byteBuffer = encoder.encode(charBuffer)
+        IntSerializer.serialize(buffer, byteBuffer.remaining() + trailingNullBytes, features)
+        buffer.put(byteBuffer)
+        for (i in 0 until trailingNullBytes)
+          buffer.put(0)
       }
     } catch (e: Throwable) {
       throw RuntimeException(data, e)
     }
+
+  fun serialize(data: String?): ByteBuffer = try {
+    if (data == null) {
+      ByteBuffer.allocate(0)
+    } else {
+      val charBuffer = charBuffer(data.length)
+      charBuffer.put(data)
+      charBuffer.flip()
+      encoder.reset()
+      encoder.encode(charBuffer)
+    }
+  } catch (e: Throwable) {
+    throw RuntimeException(data, e)
   }
 
-  fun deserializeAll(buffer: ByteBuffer): String? = preventThreadRaces {
-    try {
-      val len = buffer.remaining()
-      if (len == -1) {
-        null
-      } else {
-        val limit = buffer.limit()
-        buffer.limit(buffer.position() + len - trailingNullBytes)
-        val charBuffer = charBuffer(len)
-        decoder.reset()
-        decoder.decode(buffer, charBuffer, true)
-        buffer.limit(limit)
-        buffer.position(buffer.position() + trailingNullBytes)
-        charBuffer.flip()
-        charBuffer.toString()
-      }
-    } catch (e: Throwable) {
-      buffer.hexDump()
-      throw RuntimeException(e)
+  fun deserializeAll(buffer: ByteBuffer): String? = try {
+    val len = buffer.remaining()
+    if (len == -1) {
+      null
+    } else {
+      val limit = buffer.limit()
+      buffer.limit(buffer.position() + len - trailingNullBytes)
+      val charBuffer = charBuffer(len)
+      decoder.reset()
+      decoder.decode(buffer, charBuffer, true)
+      buffer.limit(limit)
+      buffer.position(buffer.position() + trailingNullBytes)
+      charBuffer.flip()
+      charBuffer.toString()
     }
+  } catch (e: Throwable) {
+    buffer.hexDump()
+    throw RuntimeException(e)
   }
 
-  override fun deserialize(buffer: ByteBuffer, features: Quassel_Features): String? =
-    preventThreadRaces {
-      try {
-        val len = IntSerializer.deserialize(buffer, features)
-        if (len == -1) {
-          null
-        } else {
-          val limit = buffer.limit()
-          buffer.limit(buffer.position() + Math.max(0, len - trailingNullBytes))
-          val charBuffer = charBuffer(len)
-          decoder.decode(buffer, charBuffer, true)
-          buffer.limit(limit)
-          buffer.position(buffer.position() + trailingNullBytes)
-          charBuffer.flip()
-          charBuffer.toString()
-        }
-      } catch (e: Throwable) {
-        buffer.hexDump()
-        throw RuntimeException(e)
-      }
+  override fun deserialize(buffer: ByteBuffer, features: Quassel_Features): String? = try {
+    val len = IntSerializer.deserialize(buffer, features)
+    if (len == -1) {
+      null
+    } else {
+      val limit = buffer.limit()
+      buffer.limit(buffer.position() + Math.max(0, len - trailingNullBytes))
+      val charBuffer = charBuffer(len)
+      decoder.decode(buffer, charBuffer, true)
+      buffer.limit(limit)
+      buffer.position(buffer.position() + trailingNullBytes)
+      charBuffer.flip()
+      charBuffer.toString()
     }
+  } catch (e: Throwable) {
+    buffer.hexDump()
+    throw RuntimeException(e)
+  }
 }
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/IrcUser.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/IrcUser.kt
index 06d7c06bab8fc1a8c0e6e85197ac9207db25c0c2..84fa55d9ce29dab56a5980e6855a0b3e6204ca89 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/IrcUser.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/IrcUser.kt
@@ -68,70 +68,30 @@ class IrcUser(
     setUserModes(properties["userModes"].valueOr(this::userModes))
   }
 
-  fun nick() = _nick
-  fun liveNick(): Observable<String> = live_nick
+  fun updates(): Observable<IrcUser> = hasChangedNotification.map { this }
 
+  fun nick() = _nick
   fun user() = _user
-  fun liveUser(): Observable<String> = live_user
-
   fun host() = _host
-  fun liveHost(): Observable<String> = live_host
-
   fun realName() = _realName
-  fun liveRealName(): Observable<String> = live_realName
-
   fun account() = _account
-  fun liveAccount(): Observable<String> = live_account
-
   fun hostMask() = "${nick()}!${user()}@${host()}"
-  fun liveHostMask() = liveNick().switchMap { nick ->
-    liveUser().switchMap { user ->
-      liveHost().map { host ->
-        "$nick!$user@$host"
-      }
-    }
-  }
-
   fun isAway() = _away
-  fun liveIsAway(): Observable<Boolean> = live_away
-
   fun awayMessage() = _awayMessage
-  fun liveAwayMessage(): Observable<String> = live_awayMessage
-
   fun server() = _server
-  fun liveServer(): Observable<String> = live_server
-
   fun idleTime(): Instant {
     if (Instant.now().epochSecond - _idleTimeSet.epochSecond > 1200)
       _idleTime = Instant.EPOCH
     return _idleTime
   }
-
-  fun liveIdleTime(): Observable<Instant> = live_idleTime
-
   fun loginTime() = _loginTime
-  fun liveLoginTime(): Observable<Instant> = live_loginTime
-
   fun ircOperator() = _ircOperator
-  fun liveIrcOperator(): Observable<String> = live_ircOperator
-
   fun lastAwayMessage() = _lastAwayMessage
-  fun liveLastAwayMessage(): Observable<Int> = live_lastAwayMessage
-
   fun whoisServiceReply() = _whoisServiceReply
-  fun liveWhoisServiceReply(): Observable<String> = live_whoisServiceReply
-
   fun suserHost() = _suserHost
-  fun liveSuserHost(): Observable<String> = live_suserHost
-
   fun encrypted() = _encrypted
-  fun liveEncrypted(): Observable<Boolean> = live_encrypted
-
   fun network() = _network
-
   fun userModes() = _userModes
-  fun liveUserModes(): Observable<String> = live_userModes
-
   fun channels() = _channels.map(IrcChannel::name)
   fun codecForEncoding() = _codecForEncoding
   fun codecForDecoding() = _codecForDecoding
@@ -313,92 +273,106 @@ class IrcUser(
     renameObject(identifier)
   }
 
-  private val live_nick = BehaviorSubject.createDefault(HostmaskHelper.nick(hostmask))
-  private var _nick: String
-    get() = live_nick.value
-    set(value) = live_nick.onNext(value)
-
-  private val live_user = BehaviorSubject.createDefault(HostmaskHelper.user(hostmask))
-  private var _user: String
-    get() = live_user.value
-    set(value) = live_user.onNext(value)
-
-  private val live_host = BehaviorSubject.createDefault(HostmaskHelper.host(hostmask))
-  private var _host: String
-    get() = live_host.value
-    set(value) = live_host.onNext(value)
-
-  private val live_realName = BehaviorSubject.createDefault("")
-  private var _realName: String
-    get() = live_realName.value
-    set(value) = live_realName.onNext(value)
-
-  private val live_account = BehaviorSubject.createDefault("")
-  private var _account: String
-    get() = live_account.value
-    set(value) = live_account.onNext(value)
-
-  private val live_awayMessage = BehaviorSubject.createDefault("")
-  private var _awayMessage: String
-    get() = live_awayMessage.value
-    set(value) = live_awayMessage.onNext(value)
-
-  private val live_away = BehaviorSubject.createDefault(false)
-  private var _away: Boolean
-    get() = live_away.value
-    set(value) = live_away.onNext(value)
-
-  private val live_server = BehaviorSubject.createDefault("")
-  private var _server: String
-    get() = live_server.value
-    set(value) = live_server.onNext(value)
-
-  private val live_idleTime = BehaviorSubject.createDefault(Instant.EPOCH)
-  private var _idleTime: Instant
-    get() = live_idleTime.value
-    set(value) = live_idleTime.onNext(value)
-
-  private val live_idleTimeSet = BehaviorSubject.createDefault(Instant.EPOCH)
-  private var _idleTimeSet: Instant
-    get() = live_idleTimeSet.value
-    set(value) = live_idleTimeSet.onNext(value)
-
-  private val live_loginTime = BehaviorSubject.createDefault(Instant.EPOCH)
-  private var _loginTime: Instant
-    get() = live_loginTime.value
-    set(value) = live_loginTime.onNext(value)
-
-  private val live_ircOperator = BehaviorSubject.createDefault("")
-  private var _ircOperator: String
-    get() = live_ircOperator.value
-    set(value) = live_ircOperator.onNext(value)
-
-  private val live_lastAwayMessage = BehaviorSubject.createDefault(0)
-  private var _lastAwayMessage: Int
-    get() = live_lastAwayMessage.value
-    set(value) = live_lastAwayMessage.onNext(value)
-
-  private val live_whoisServiceReply = BehaviorSubject.createDefault("")
-  private var _whoisServiceReply: String
-    get() = live_whoisServiceReply.value
-    set(value) = live_whoisServiceReply.onNext(value)
-
-  private val live_suserHost = BehaviorSubject.createDefault("")
-  private var _suserHost: String
-    get() = live_suserHost.value
-    set(value) = live_suserHost.onNext(value)
-
-  private val live_encrypted = BehaviorSubject.createDefault(false)
-  private var _encrypted: Boolean
-    get() = live_encrypted.value
-    set(value) = live_encrypted.onNext(value)
+  private val hasChangedNotification = BehaviorSubject.createDefault(Unit)
+  private var _nick: String = HostmaskHelper.nick(hostMask())
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _user: String = HostmaskHelper.user(hostMask())
+
+  private var _host: String = HostmaskHelper.host(hostMask())
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _realName: String = ""
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _account: String = ""
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _awayMessage: String = ""
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _away: Boolean = false
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _server: String = ""
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _idleTime: Instant = Instant.EPOCH
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _idleTimeSet: Instant = Instant.EPOCH
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _loginTime: Instant = Instant.EPOCH
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _ircOperator: String = ""
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _lastAwayMessage: Int = 0
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _whoisServiceReply: String = ""
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _suserHost: String = ""
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
+
+  private var _encrypted: Boolean = false
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
 
   private var _channels: MutableSet<IrcChannel> = mutableSetOf()
 
-  private val live_userModes = BehaviorSubject.createDefault("")
-  private var _userModes: String
-    get() = live_userModes.value
-    set(value) = live_userModes.onNext(value)
+  private var _userModes: String = ""
+    set(value) {
+      field = value
+      hasChangedNotification.onNext(Unit)
+    }
 
   private var _network: Network = network
   private var _codecForEncoding: Charset? = null
@@ -407,4 +381,4 @@ class IrcUser(
   companion object {
     val NULL = IrcUser("", Network.NULL, SignalProxy.NULL)
   }
-}
+}
\ No newline at end of file
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt b/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt
index b1703df15cc9606119d2f7ae5d195d964842f938..428cbdfb05df6bba395e0a5f6b4c3b35557e48b8 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt
@@ -15,6 +15,8 @@ import java.io.Closeable
 
 @Suppress("LeakingThis")
 abstract class ProtocolHandler : SignalProxy, AuthHandler, Closeable {
+  protected var closed = false
+
   private val objectStorage: ObjectStorage = ObjectStorage(this)
 
   protected open var rpcHandler: RpcHandler? = null
@@ -33,6 +35,8 @@ abstract class ProtocolHandler : SignalProxy, AuthHandler, Closeable {
   private var totalInitCount = 0
 
   override fun handle(f: SignalProxyMessage): Boolean {
+    if (closed) return true
+
     try {
       if (!super<SignalProxy>.handle(f)) {
         log(DEBUG, "ProtocolHandler", "No receiver registered for $f")
@@ -44,6 +48,8 @@ abstract class ProtocolHandler : SignalProxy, AuthHandler, Closeable {
   }
 
   override fun handle(f: HandshakeMessage): Boolean {
+    if (closed) return true
+
     try {
       if (!super<AuthHandler>.handle(f)) {
         log(DEBUG, "ProtocolHandler", "No receiver registered for $f")
@@ -177,6 +183,8 @@ abstract class ProtocolHandler : SignalProxy, AuthHandler, Closeable {
   }
 
   override fun close() {
+    closed = true
+
     objectStorage.clear()
     toInit.clear()
   }
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
index 4cf4a7c1b4842e0284230cc456ee3a5a2642e507..d3b160dbe4184f68a9ec077da193b9c86e00334a 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
@@ -7,8 +7,7 @@ import de.kuschku.libquassel.quassel.QuasselFeature
 import de.kuschku.libquassel.quassel.syncables.*
 import de.kuschku.libquassel.util.compatibility.HandlerService
 import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log
-import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.DEBUG
-import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.INFO
+import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.*
 import de.kuschku.libquassel.util.hasFlag
 import io.reactivex.subjects.BehaviorSubject
 import org.threeten.bp.Instant
@@ -54,6 +53,7 @@ class Session(
   override val lag = BehaviorSubject.createDefault(0L)
 
   init {
+    log(ERROR, "DEBUG", "created session:", RuntimeException())
     coreConnection.start()
   }
 
@@ -159,11 +159,15 @@ class Session(
   }
 
   override fun dispatch(message: SignalProxyMessage) {
+    if (closed) return
+
     log(DEBUG, "Session", "> $message")
     coreConnection.dispatch(message)
   }
 
   override fun dispatch(message: HandshakeMessage) {
+    if (closed) return
+
     log(DEBUG, "Session", "> $message")
     coreConnection.dispatch(message)
   }
@@ -172,13 +176,13 @@ class Session(
   override fun identity(id: IdentityId): Identity? = identities[id]
 
   override fun close() {
+    super.close()
+
     coreConnection.close()
 
     certManagers.clear()
     identities.clear()
     networks.clear()
-
-    super.close()
   }
 
   fun join() {
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
index 0333859a47a24cc8d0f5924af775465422b74773..9294077913652e754e5a0c4666f10eef9aa2c824 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
@@ -84,7 +84,7 @@ class SessionManager(offlineSession: ISession,
     log(LoggingHandler.LogLevel.INFO, "Session", "Session created")
 
     state.subscribe {
-      if (state == ConnectionState.CONNECTED) {
+      if (it == ConnectionState.CONNECTED) {
         lastSession.close()
       }
     }
diff --git a/lib/src/main/java/de/kuschku/libquassel/util/Optional.kt b/lib/src/main/java/de/kuschku/libquassel/util/Optional.kt
new file mode 100644
index 0000000000000000000000000000000000000000..390581dccb365cb615a6db858e60553660713001
--- /dev/null
+++ b/lib/src/main/java/de/kuschku/libquassel/util/Optional.kt
@@ -0,0 +1,58 @@
+package de.kuschku.libquassel.util
+
+import java.io.Serializable
+
+interface Optional<T> : Serializable {
+  fun get(): T
+  fun isPresent(): Boolean
+  fun ifPresent(consumer: (T) -> Unit)
+  fun filter(predicate: (T) -> Boolean): Optional<T>
+  fun <U> map(mapper: (T) -> U): Optional<U>
+  fun <U> flatMap(mapper: (T) -> Optional<U>): Optional<U>
+  fun orElse(other: T): T
+  fun orNull(): T?
+  fun <X : Throwable> orElseThrow(supplier: () -> X): T
+  override fun equals(other: Any?): Boolean
+  override fun hashCode(): Int
+  override fun toString(): String
+
+  private class Present<T>(private val value: T) : Optional<T> {
+    override fun get() = value
+    override fun isPresent() = true
+    override fun ifPresent(consumer: (T) -> Unit) = consumer(value)
+    override fun filter(predicate: (T) -> Boolean) = if (predicate(value)) this else empty<T>()
+    override fun <U> map(mapper: (T) -> U) = ofNullable(mapper(value))
+    override fun <U> flatMap(mapper: (T) -> Optional<U>) = mapper(value)
+    override fun orElse(other: T) = value
+    override fun orNull(): T? = value
+    override fun <X : Throwable> orElseThrow(supplier: () -> X) = value
+    override fun equals(other: Any?) = (other as? Present<*>)?.value == value
+    override fun hashCode() = value?.hashCode() ?: 0
+    override fun toString() = "Optional[$value]"
+  }
+
+  private class Absent<T> : Optional<T> {
+    override fun get() = throw NoSuchElementException("No value present")
+    override fun isPresent() = false
+    override fun ifPresent(consumer: (T) -> Unit) = Unit
+    override fun filter(predicate: (T) -> Boolean) = this
+    override fun <U> map(mapper: (T) -> U) = empty<U>()
+    override fun <U> flatMap(mapper: (T) -> Optional<U>) = empty<U>()
+    override fun orElse(other: T) = other
+    override fun orNull(): T? = null
+    override fun <X : Throwable> orElseThrow(supplier: () -> X) = throw supplier()
+    override fun equals(other: Any?) = other === this
+    override fun hashCode() = 0
+    override fun toString() = "Optional.empty"
+  }
+
+  companion object {
+    private val absent = Absent<Any?>()
+
+    @Suppress("UNCHECKED_CAST")
+    fun <T> empty(): Optional<T> = absent as Absent<T>
+
+    fun <T> of(value: T): Optional<T> = Present(value)
+    fun <T> ofNullable(value: T?): Optional<T> = value?.let(::Present) ?: empty()
+  }
+}
\ No newline at end of file
diff --git a/lib/src/main/java/de/kuschku/libquassel/util/helpers/ObservableHelper.kt b/lib/src/main/java/de/kuschku/libquassel/util/helpers/ObservableHelper.kt
index edc47fcedb6323e9bad8a75fd4204eddbb6fb805..90facf688a16091b7ceb37602854030f387c7f7c 100644
--- a/lib/src/main/java/de/kuschku/libquassel/util/helpers/ObservableHelper.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/util/helpers/ObservableHelper.kt
@@ -7,3 +7,6 @@ fun <T> Observable<T>.or(default: T): T = try {
 } catch (_: Throwable) {
   default
 }
+
+val <T> Observable<T>.value
+  get() = this.blockingLatest().firstOrNull()
\ No newline at end of file
diff --git a/malheur/build.gradle.kts b/malheur/build.gradle.kts
index 474d61213e8e08415a89312cf5aa7e720bbbac00..ae8c5953619a9d423cba923603dddaa8f9507a77 100644
--- a/malheur/build.gradle.kts
+++ b/malheur/build.gradle.kts
@@ -17,7 +17,7 @@ android {
 }
 
 dependencies {
-  implementation(kotlin("stdlib", "1.2.30"))
+  implementation(kotlin("stdlib", "1.2.31"))
 
   implementation("com.google.code.gson", "gson", "2.8.2")
 }
diff --git a/persistence/build.gradle.kts b/persistence/build.gradle.kts
index c470a79b3bb77d3f319d09b50e6853a2253452a4..8613c62d8ece9fd0b338b739d6544be9a9aca1cd 100644
--- a/persistence/build.gradle.kts
+++ b/persistence/build.gradle.kts
@@ -23,7 +23,7 @@ android {
 }
 
 dependencies {
-  implementation(kotlin("stdlib", "1.2.30"))
+  implementation(kotlin("stdlib", "1.2.31"))
 
   // App Compat
   withVersion("27.1.0") {
diff --git a/viewmodel/build.gradle.kts b/viewmodel/build.gradle.kts
index 0be2deb0b9626e83365891960030a72173e0ea3f..1b1afedb6578471f16ba94606c6b7f3cf806e743 100644
--- a/viewmodel/build.gradle.kts
+++ b/viewmodel/build.gradle.kts
@@ -17,7 +17,7 @@ android {
 }
 
 dependencies {
-  implementation(kotlin("stdlib", "1.2.30"))
+  implementation(kotlin("stdlib", "1.2.31"))
 
   // App Compat
   withVersion("27.1.0") {
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/util/helper/ObservableHelper.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/util/helper/ObservableHelper.kt
index 891516836e2feffc0fd708f248a2adc0e6ab4cbb..2e725b3625b3d2084dd9e1a198312d366d4d995d 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/util/helper/ObservableHelper.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/util/helper/ObservableHelper.kt
@@ -5,10 +5,27 @@ import android.arch.lifecycle.LiveDataReactiveStreams
 import io.reactivex.BackpressureStrategy
 import io.reactivex.Observable
 import io.reactivex.ObservableSource
+import io.reactivex.functions.BiFunction
 
 inline fun <T> Observable<T>.toLiveData(
   strategy: BackpressureStrategy = BackpressureStrategy.LATEST
 ): LiveData<T> = LiveDataReactiveStreams.fromPublisher(toFlowable(strategy))
 
+inline fun <reified A, B> combineLatest(
+  a: ObservableSource<A>,
+  b: ObservableSource<B>
+): Observable<Pair<A, B>> = Observable.combineLatest(a, b, BiFunction(::Pair))
+
+inline fun <reified A, B, C> combineLatest(
+  a: ObservableSource<A>,
+  b: ObservableSource<B>,
+  c: ObservableSource<C>
+): Observable<Triple<A, B, C>> = Observable.combineLatest(listOf(a, b, c), { (t0, t1, t2) ->
+  Triple(t0, t1, t2) as Triple<A, B, C>
+})
+
 inline fun <reified T> combineLatest(sources: Iterable<ObservableSource<out T>?>) =
-  Observable.combineLatest(sources) { t -> t.toList() as List<T> }
\ No newline at end of file
+  Observable.combineLatest(sources) { t -> t.toList() as List<T> }
+
+inline operator fun <T, U> Observable<T>.invoke(f: (T) -> U?) =
+  blockingLatest().firstOrNull()?.let(f)
\ No newline at end of file
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
index e2500ca47102bbf74c2eb39123766c4a185cc310..1eb9a5b0ce6f702d6b1062a2b52a25c2dd87971d 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
@@ -1,101 +1,89 @@
 package de.kuschku.quasseldroid.viewmodel
 
-import android.arch.lifecycle.LiveData
-import android.arch.lifecycle.MutableLiveData
 import android.arch.lifecycle.ViewModel
 import de.kuschku.libquassel.protocol.BufferId
 import de.kuschku.libquassel.protocol.Buffer_Type
 import de.kuschku.libquassel.protocol.NetworkId
 import de.kuschku.libquassel.quassel.BufferInfo
-import de.kuschku.libquassel.quassel.syncables.BufferViewConfig
-import de.kuschku.libquassel.quassel.syncables.IrcChannel
-import de.kuschku.libquassel.quassel.syncables.IrcUser
-import de.kuschku.libquassel.quassel.syncables.Network
+import de.kuschku.libquassel.quassel.syncables.*
 import de.kuschku.libquassel.session.Backend
 import de.kuschku.libquassel.session.ISession
 import de.kuschku.libquassel.session.SessionManager
+import de.kuschku.libquassel.util.Optional
 import de.kuschku.libquassel.util.and
 import de.kuschku.libquassel.util.hasFlag
-import de.kuschku.quasseldroid.util.helper.*
+import de.kuschku.quasseldroid.util.helper.combineLatest
+import de.kuschku.quasseldroid.util.helper.toLiveData
 import de.kuschku.quasseldroid.viewmodel.data.*
 import io.reactivex.Observable
+import io.reactivex.functions.Function
+import io.reactivex.subjects.BehaviorSubject
 import java.util.concurrent.TimeUnit
 
 class QuasselViewModel : ViewModel() {
-  private val backendWrapper = MutableLiveData<LiveData<Backend?>>()
-  fun setBackend(backendWrapper: LiveData<Backend?>) {
-    this.backendWrapper.value = backendWrapper
-  }
+  val backendWrapper = BehaviorSubject.createDefault(Observable.empty<Optional<Backend>>())
 
-  val buffer = MutableLiveData<BufferId>()
+  val buffer = BehaviorSubject.createDefault(-1)
+  val buffer_liveData = buffer.toLiveData()
 
-  private val bufferViewConfigId = MutableLiveData<Int?>()
-  fun getBufferViewConfigId(): LiveData<Int?> = bufferViewConfigId
-  fun setBufferViewConfigId(bufferViewConfig: Int?) {
-    this.bufferViewConfigId.value = bufferViewConfig
-  }
+  val bufferViewConfigId = BehaviorSubject.createDefault(-1)
 
   val MAX_RECENT_MESSAGES = 20
-  val recentlySentMessages = MutableLiveData<List<CharSequence>>()
+  val recentlySentMessages = BehaviorSubject.createDefault(emptyList<CharSequence>())
+  val recentlySentMessages_liveData = recentlySentMessages.toLiveData()
   fun addRecentlySentMessage(message: CharSequence) {
-    recentlySentMessages.value =
-      listOf(message) +
-      recentlySentMessages.value.orEmpty()
+    recentlySentMessages.onNext(
+      listOf(message) + recentlySentMessages.value
         .filter { it != message }
         .take(MAX_RECENT_MESSAGES - 1)
+    )
   }
 
   val backend = backendWrapper.switchMap { it }
-  val sessionManager = backend.map(Backend::sessionManager)
-  val session = sessionManager.switchMapRx(SessionManager::session)
+  val sessionManager = backend
+    .filter(Optional<Backend>::isPresent)
+    .map(Optional<Backend>::get)
+    .map(Backend::sessionManager)
+  val sessionManager_liveData = sessionManager.toLiveData()
+  val session = sessionManager.switchMap(SessionManager::session)
 
-  val connectionProgress = sessionManager.switchMapRx(SessionManager::connectionProgress)
+  val connectionProgress = sessionManager.switchMap(SessionManager::connectionProgress)
+  val connectionProgress_liveData = connectionProgress.toLiveData()
 
-  private val bufferViewManager = session.map(ISession::bufferViewManager)
+  val bufferViewManager = session.map { Optional.ofNullable(it.bufferViewManager) }
+    .filter(Optional<BufferViewManager>::isPresent)
+    .map(Optional<BufferViewManager>::get)
 
   val bufferViewConfig = bufferViewManager.switchMap { manager ->
-    bufferViewConfigId.map { id ->
-      manager.bufferViewConfig(id)
-    }
+    bufferViewConfigId.map(Function<Int, Optional<BufferViewConfig>> { id ->
+      Optional.ofNullable(manager.bufferViewConfig(id))
+    })
   }
 
-  val errors = session.switchMapRx(ISession::error)
+  val errors = session.switchMap(ISession::error)
+  val errors_liveData = errors.toLiveData()
 
-  private var lastMarkerLine = -1
   /**
    * An observable of the changes of the markerline, as pairs of `(old, new)`
    */
   val markerLine = session.switchMap { currentSession ->
-    buffer.switchMapRx { currentBuffer ->
+    buffer.switchMap { currentBuffer ->
       // Get a stream of the latest marker line
-      val raw = currentSession.bufferSyncer?.liveMarkerLine(currentBuffer)
-
-      // Turn it into a pair of changes
-      val changes = raw?.map {
-        val previous = lastMarkerLine
-        if (it != lastMarkerLine)
-          lastMarkerLine = it
-        previous to it
-      }
-
-      // Only return when there was an actual change
-      val distinct = changes?.filter {
-        it.first != it.second
-      }
-
-      distinct
+      val raw = currentSession.bufferSyncer?.liveMarkerLine(currentBuffer) ?: Observable.empty()
+      raw.scan(Pair(-1, -1)) { (_, previous), next -> Pair(previous, next) }
     }
   }
+  val markerLine_liveData = markerLine.toLiveData()
 
-  val lag: LiveData<Long?> = session.switchMapRx(ISession::lag)
+  val lag: Observable<Long> = session.switchMap(ISession::lag)
 
-  val isSecure: LiveData<Boolean?> = session.switchMapRx { session ->
-      session.state.map { _ ->
-        session.sslSession != null
-      }
+  val isSecure: Observable<Boolean> = session.switchMap { session ->
+    session.state.map { _ ->
+      session.sslSession != null
     }
+  }
 
-  val bufferData = session.zip(buffer).switchMapRx { (session, id) ->
+  val bufferData = combineLatest(session, buffer).switchMap { (session, id) ->
     val bufferSyncer = session?.bufferSyncer
     if (bufferSyncer != null) {
       bufferSyncer.liveBufferInfos().switchMap {
@@ -106,12 +94,12 @@ class QuasselViewModel : ViewModel() {
         } else {
           when (info.type.toInt()) {
             BufferInfo.Type.QueryBuffer.toInt()   -> {
-              network.liveIrcUser(info.bufferName).switchMap { user ->
-                user.liveRealName().map { realName ->
+              network.liveIrcUser(info.bufferName).switchMap {
+                it.updates().map { user ->
                   BufferData(
                     info = info,
                     network = network.networkInfo(),
-                    description = realName
+                    description = user.realName()
                   )
                 }
               }
@@ -150,162 +138,139 @@ class QuasselViewModel : ViewModel() {
     }
   }
 
-  val nickData: LiveData<List<IrcUserItem>?> = session.zip(
-    buffer
-  ).switchMapRx { (session, buffer) ->
-    val bufferSyncer = session?.bufferSyncer
-    val bufferInfo = bufferSyncer?.bufferInfo(buffer)
-    if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) {
-      val network = session.networks[bufferInfo.networkId]
-      val ircChannel = network?.ircChannel(bufferInfo.bufferName)
-      if (ircChannel != null) {
-        ircChannel.liveIrcUsers().switchMap { users ->
-          combineLatest<IrcUserItem>(
-            users.map<IrcUser, Observable<IrcUserItem>?> { user ->
-              user.liveNick().switchMap { nick ->
-                user.liveRealName().switchMap { realName ->
-                  user.liveIsAway().map { away ->
-                    val userModes = ircChannel.userModes(user)
-                    val prefixModes = network.prefixModes()
-
-                    val lowestMode = userModes.mapNotNull {
-                      prefixModes.indexOf(it)
-                    }.min() ?: prefixModes.size
-
-                    IrcUserItem(
-                      nick,
-                      network.modesToPrefixes(userModes),
-                      lowestMode,
-                      realName,
-                      away,
-                      network.support("CASEMAPPING")
-                    )
-                  }
+  val nickData: Observable<List<IrcUserItem>> = combineLatest(session, buffer)
+    .switchMap { (session, buffer) ->
+      val bufferSyncer = session?.bufferSyncer
+      val bufferInfo = bufferSyncer?.bufferInfo(buffer)
+      if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) {
+        val network = session.networks[bufferInfo.networkId]
+        val ircChannel = network?.ircChannel(bufferInfo.bufferName)
+        if (ircChannel != null) {
+          ircChannel.liveIrcUsers().switchMap { users ->
+            combineLatest<IrcUserItem>(
+              users.map<IrcUser, Observable<IrcUserItem>?> {
+                it.updates().map { user ->
+                  val userModes = ircChannel.userModes(user)
+                  val prefixModes = network.prefixModes()
+
+                  val lowestMode = userModes.mapNotNull {
+                    prefixModes.indexOf(it)
+                  }.min() ?: prefixModes.size
+
+                  IrcUserItem(
+                    user.nick(),
+                    network.modesToPrefixes(userModes),
+                    lowestMode,
+                    user.realName(),
+                    user.isAway(),
+                    network.support("CASEMAPPING")
+                  )
                 }
               }
-            })
+            )
+          }
+        } else {
+          Observable.just(emptyList())
         }
       } else {
         Observable.just(emptyList())
       }
-    } else {
-      Observable.just(emptyList())
     }
-  }
 
-  val lastWord = MutableLiveData<Observable<Pair<String, IntRange>>>()
-
-  val autoCompleteData: LiveData<Pair<String, List<AutoCompleteItem>>?> = session.zip(
-    buffer, lastWord
-  ).switchMapRx { (session, id, lastWordWrapper) ->
-    lastWordWrapper
-      .distinctUntilChanged()
-      .debounce(300, TimeUnit.MILLISECONDS)
-      .switchMap { lastWord ->
-        val bufferSyncer = session?.bufferSyncer
-        val bufferInfo = bufferSyncer?.bufferInfo(id)
-        if (bufferSyncer != null) {
-          bufferSyncer.liveBufferInfos().switchMap { infos ->
-            if (bufferInfo?.type?.hasFlag(
-                Buffer_Type.ChannelBuffer
-              ) == true) {
-              val network = session.networks[bufferInfo.networkId]
-              val ircChannel = network?.ircChannel(
-                bufferInfo.bufferName
-              )
-              if (ircChannel != null) {
-                ircChannel.liveIrcUsers().switchMap { users ->
-                  val buffers: List<Observable<AutoCompleteItem.ChannelItem>?> = infos.values
-                    .filter {
-                      it.type.toInt() == Buffer_Type.ChannelBuffer.toInt()
-                    }.mapNotNull { info ->
-                      session.networks[info.networkId]?.let { info to it }
-                    }.map<Pair<BufferInfo, Network>, Observable<AutoCompleteItem.ChannelItem>?> { (info, network) ->
-                      network.liveIrcChannel(
-                        info.bufferName
-                      ).switchMap { channel ->
-                        channel.liveTopic().map { topic ->
-                          AutoCompleteItem.ChannelItem(
-                            info = info,
-                            network = network.networkInfo(),
-                            bufferStatus = when (channel) {
-                              IrcChannel.NULL -> BufferStatus.OFFLINE
-                              else            -> BufferStatus.ONLINE
-                            },
-                            description = topic
-                          )
+  val lastWord = BehaviorSubject.create<Observable<Pair<String, IntRange>>>()
+
+  val autoCompleteData: Observable<Pair<String, List<AutoCompleteItem>>> =
+    combineLatest(session, buffer, lastWord).switchMap { (session, id, lastWordWrapper) ->
+      lastWordWrapper
+        .distinctUntilChanged()
+        .debounce(300, TimeUnit.MILLISECONDS)
+        .switchMap { lastWord ->
+          val bufferSyncer = session?.bufferSyncer
+          val bufferInfo = bufferSyncer?.bufferInfo(id)
+          if (bufferSyncer != null) {
+            bufferSyncer.liveBufferInfos().switchMap { infos ->
+              if (bufferInfo?.type?.hasFlag(
+                  Buffer_Type.ChannelBuffer
+                ) == true) {
+                val network = session.networks[bufferInfo.networkId]
+                val ircChannel = network?.ircChannel(
+                  bufferInfo.bufferName
+                )
+                if (ircChannel != null) {
+                  ircChannel.liveIrcUsers().switchMap { users ->
+                    val buffers: List<Observable<AutoCompleteItem.ChannelItem>?> = infos.values
+                      .filter {
+                        it.type.toInt() == Buffer_Type.ChannelBuffer.toInt()
+                      }.mapNotNull { info ->
+                        session.networks[info.networkId]?.let { info to it }
+                      }.map<Pair<BufferInfo, Network>, Observable<AutoCompleteItem.ChannelItem>?> { (info, network) ->
+                        network.liveIrcChannel(
+                          info.bufferName
+                        ).switchMap { channel ->
+                          channel.liveTopic().map { topic ->
+                            AutoCompleteItem.ChannelItem(
+                              info = info,
+                              network = network.networkInfo(),
+                              bufferStatus = when (channel) {
+                                IrcChannel.NULL -> BufferStatus.OFFLINE
+                                else            -> BufferStatus.ONLINE
+                              },
+                              description = topic
+                            )
+                          }
                         }
                       }
+                    val nicks = users.map<IrcUser, Observable<AutoCompleteItem.UserItem>?> {
+                      it.updates().map { user ->
+                        val userModes = ircChannel.userModes(user)
+                        val prefixModes = network.prefixModes()
+
+                        val lowestMode = userModes.mapNotNull(prefixModes::indexOf).min()
+                                         ?: prefixModes.size
+
+                        AutoCompleteItem.UserItem(
+                          user.nick(),
+                          network.modesToPrefixes(userModes),
+                          lowestMode,
+                          user.realName(),
+                          user.isAway(),
+                          network.support("CASEMAPPING")
+                        )
+                      }
                     }
-                  val nicks = users.map<IrcUser, Observable<AutoCompleteItem.UserItem>?> { user ->
-                    user.liveNick().switchMap { nick ->
-                      user.liveRealName().switchMap { realName ->
-                        user.liveIsAway().map { away ->
-                          val userModes = ircChannel.userModes(
-                            user
-                          )
-                          val prefixModes = network.prefixModes()
 
-                          val lowestMode = userModes.mapNotNull {
-                            prefixModes.indexOf(
-                              it
-                            )
-                          }.min() ?: prefixModes.size
+                    combineLatest<AutoCompleteItem>(nicks + buffers)
+                      .map { list ->
+                        val ignoredStartingCharacters = charArrayOf(
+                          '-', '_', '[', ']', '{', '}', '|', '`', '^', '.', '\\'
+                        )
 
-                          AutoCompleteItem.UserItem(
-                            nick,
-                            network.modesToPrefixes(
-                              userModes
-                            ),
-                            lowestMode,
-                            realName,
-                            away,
-                            network.support(
-                              "CASEMAPPING"
-                            )
-                          )
-                        }
+                        Pair(
+                          lastWord.first,
+                          list.filter {
+                            it.name.trimStart(*ignoredStartingCharacters)
+                              .startsWith(
+                                lastWord.first.trimStart(*ignoredStartingCharacters),
+                                ignoreCase = true
+                              )
+                          }.sorted()
+                        )
                       }
-                    }
                   }
-
-                  combineLatest<AutoCompleteItem>(nicks + buffers)
-                    .map { list ->
-                      val ignoredStartingCharacters = charArrayOf(
-                        '-', '_', '[', ']', '{', '}', '|', '`', '^', '.', '\\'
-                      )
-                      Pair(
-                        lastWord.first,
-                        list.filter {
-                          it.name.trimStart(*ignoredStartingCharacters)
-                            .startsWith(
-                              lastWord.first.trimStart(*ignoredStartingCharacters),
-                              ignoreCase = true
-                            )
-                        }.sorted()
-                      )
-                    }
+                } else {
+                  Observable.just(Pair(lastWord.first, emptyList()))
                 }
               } else {
-                Observable.just(
-                  Pair(lastWord.first, emptyList())
-                )
+                Observable.just(Pair(lastWord.first, emptyList()))
               }
-            } else {
-              Observable.just(
-                Pair(lastWord.first, emptyList())
-              )
             }
+          } else {
+            Observable.just(Pair(lastWord.first, emptyList()))
           }
-        } else {
-          Observable.just(
-            Pair(lastWord.first, emptyList())
-          )
         }
-      }
-  }
+    }
 
-  val bufferViewConfigs = bufferViewManager.switchMapRx { manager ->
+  val bufferViewConfigs = bufferViewManager.switchMap { manager ->
     manager.liveBufferViewConfigs().map { ids ->
       ids.mapNotNull { id ->
         manager.bufferViewConfig(id)
@@ -313,193 +278,185 @@ class QuasselViewModel : ViewModel() {
     }
   }
 
-  val showHidden = MutableLiveData<Boolean>()
-  val collapsedNetworks = MutableLiveData<Set<NetworkId>>()
-  val selectedBufferId = MutableLiveData<BufferId>()
-  val selectedBuffer = session.zip(
-    selectedBufferId, bufferViewConfig
-  ).switchMapRx { (session, buffer, bufferViewConfig) ->
-    val bufferSyncer = session?.bufferSyncer
-    if (bufferSyncer != null && bufferViewConfig != null) {
-      val hiddenState = when {
-        bufferViewConfig.removedBuffers().contains(buffer)            ->
-          BufferHiddenState.HIDDEN_PERMANENT
-        bufferViewConfig.temporarilyRemovedBuffers().contains(buffer) ->
-          BufferHiddenState.HIDDEN_TEMPORARY
-        else                                                          ->
-          BufferHiddenState.VISIBLE
-      }
+  val showHidden = BehaviorSubject.createDefault(false)
+  val collapsedNetworks = BehaviorSubject.createDefault(emptySet<NetworkId>())
+  val selectedBufferId = BehaviorSubject.createDefault(-1)
+  val selectedBuffer = combineLatest(session, selectedBufferId, bufferViewConfig)
+    .switchMap { (session, buffer, bufferViewConfigOptional) ->
+      val bufferSyncer = session.bufferSyncer
+      val bufferViewConfig = bufferViewConfigOptional.orNull()
+      if (bufferSyncer != null && bufferViewConfig != null) {
+        val hiddenState = when {
+          bufferViewConfig.removedBuffers().contains(buffer)            ->
+            BufferHiddenState.HIDDEN_PERMANENT
+          bufferViewConfig.temporarilyRemovedBuffers().contains(buffer) ->
+            BufferHiddenState.HIDDEN_TEMPORARY
+          else                                                          ->
+            BufferHiddenState.VISIBLE
+        }
 
-      val info = bufferSyncer.bufferInfo(buffer)
-      if (info != null) {
-        val network = session.networks[info.networkId]
-        when (info.type.enabledValues().firstOrNull()) {
-          Buffer_Type.StatusBuffer  -> {
-            network?.liveConnectionState?.map {
-              SelectedBufferItem(
-                info,
-                connectionState = it,
-                hiddenState = hiddenState
-              )
+        val info = bufferSyncer.bufferInfo(buffer)
+        if (info != null) {
+          val network = session.networks[info.networkId]
+          when (info.type.enabledValues().firstOrNull()) {
+            Buffer_Type.StatusBuffer  -> {
+              network?.liveConnectionState?.map {
+                SelectedBufferItem(
+                  info,
+                  connectionState = it,
+                  hiddenState = hiddenState
+                )
+              } ?: Observable.just(SelectedBufferItem(info, hiddenState = hiddenState))
             }
-          }
-          Buffer_Type.ChannelBuffer -> {
-            network?.liveIrcChannel(info.bufferName)?.map {
-              SelectedBufferItem(
-                info,
-                joined = it != IrcChannel.NULL,
-                hiddenState = hiddenState
-              )
+            Buffer_Type.ChannelBuffer -> {
+              network?.liveIrcChannel(info.bufferName)?.map {
+                SelectedBufferItem(
+                  info,
+                  joined = it != IrcChannel.NULL,
+                  hiddenState = hiddenState
+                )
+              } ?: Observable.just(SelectedBufferItem(info, hiddenState = hiddenState))
             }
+            else                      ->
+              Observable.just(SelectedBufferItem(info, hiddenState = hiddenState))
           }
-          else                      ->
-            Observable.just(SelectedBufferItem(info, hiddenState = hiddenState))
+        } else {
+          Observable.just(SelectedBufferItem())
         }
       } else {
-        Observable.just(SelectedBufferItem(info, hiddenState = hiddenState))
+        Observable.just(SelectedBufferItem())
       }
-    } else {
-      Observable.just(SelectedBufferItem())
     }
-  }
+  val selectedBuffer_liveData = selectedBuffer.toLiveData()
 
-  val bufferList: LiveData<Pair<BufferViewConfig?, List<BufferProps>>?> = session.zip(
-    bufferViewConfig, showHidden
-  ).switchMapRx { (session, config, showHiddenRaw) ->
-    val bufferSyncer = session?.bufferSyncer
-    val showHidden = showHiddenRaw ?: false
-    if (bufferSyncer != null && config != null) {
-      config.live_config.debounce(16, TimeUnit.MILLISECONDS).switchMap { currentConfig ->
-        combineLatest<Collection<BufferId>>(
-          listOf(
-            config.live_buffers,
-            config.live_temporarilyRemovedBuffers,
-            config.live_removedBuffers
-          )
-        ).switchMap { (ids, temp, perm) ->
-          fun transformIds(ids: Collection<BufferId>, state: BufferHiddenState) =
-            ids.mapNotNull { id ->
-              bufferSyncer.bufferInfo(id)
-            }.filter {
-              currentConfig.networkId() <= 0 || currentConfig.networkId() == it.networkId
-            }.filter {
-              (currentConfig.allowedBufferTypes() and it.type).isNotEmpty() ||
-              it.type.hasFlag(Buffer_Type.StatusBuffer)
-            }.mapNotNull {
-              val network = session.networks[it.networkId]
-              if (network == null) {
-                null
-              } else {
-                it to network
-              }
-            }.map<Pair<BufferInfo, Network>, Observable<BufferProps>?> { (info, network) ->
-              bufferSyncer.liveActivity(info.bufferId).switchMap { activity ->
-                bufferSyncer.liveHighlightCount(info.bufferId).map { highlights ->
-                  activity to highlights
-                }
-              }.switchMap { (activity, highlights) ->
-                when (info.type.toInt()) {
-                  BufferInfo.Type.QueryBuffer.toInt()   -> {
-                    network.liveIrcUser(info.bufferName).switchMap { user ->
-                      user.liveIsAway().switchMap { away ->
-                        user.liveRealName().map { realName ->
+  val bufferList: Observable<Pair<BufferViewConfig?, List<BufferProps>>?> =
+    combineLatest(session, bufferViewConfig, showHidden)
+      .switchMap { (session, configOptional, showHiddenRaw) ->
+        val bufferSyncer = session?.bufferSyncer
+        val showHidden = showHiddenRaw ?: false
+        val config = configOptional.orNull()
+        if (bufferSyncer != null && config != null) {
+          config.live_config
+            .debounce(16, TimeUnit.MILLISECONDS)
+            .switchMap { currentConfig ->
+              combineLatest<Collection<BufferId>>(
+                listOf(
+                  config.live_buffers,
+                  config.live_temporarilyRemovedBuffers,
+                  config.live_removedBuffers
+                )
+              ).switchMap { (ids, temp, perm) ->
+                fun transformIds(ids: Collection<BufferId>, state: BufferHiddenState) =
+                  ids.mapNotNull { id ->
+                    bufferSyncer.bufferInfo(id)
+                  }.filter {
+                    currentConfig.networkId() <= 0 || currentConfig.networkId() == it.networkId
+                  }.filter {
+                    (currentConfig.allowedBufferTypes() and it.type).isNotEmpty() ||
+                    it.type.hasFlag(Buffer_Type.StatusBuffer)
+                  }.mapNotNull {
+                    val network = session.networks[it.networkId]
+                    if (network == null) {
+                      null
+                    } else {
+                      it to network
+                    }
+                  }.map<Pair<BufferInfo, Network>, Observable<BufferProps>?> { (info, network) ->
+                    bufferSyncer.liveActivity(info.bufferId).switchMap { activity ->
+                      bufferSyncer.liveHighlightCount(info.bufferId).map { highlights ->
+                        activity to highlights
+                      }
+                    }.switchMap { (activity, highlights) ->
+                      when (info.type.toInt()) {
+                        BufferInfo.Type.QueryBuffer.toInt()   -> {
+                          network.liveIrcUser(info.bufferName).switchMap {
+                            it.updates().map { user ->
+                              BufferProps(
+                                info = info,
+                                network = network.networkInfo(),
+                                bufferStatus = when {
+                                  user == IrcUser.NULL -> BufferStatus.OFFLINE
+                                  user.isAway()        -> BufferStatus.AWAY
+                                  else                 -> BufferStatus.ONLINE
+                                },
+                                description = user.realName(),
+                                activity = activity,
+                                highlights = highlights,
+                                hiddenState = state
+                              )
+                            }
+                          }
+                        }
+                        BufferInfo.Type.ChannelBuffer.toInt() -> {
+                          network.liveIrcChannel(info.bufferName).switchMap { channel ->
+                            channel.liveTopic().map { topic ->
+                              BufferProps(
+                                info = info,
+                                network = network.networkInfo(),
+                                bufferStatus = when (channel) {
+                                  IrcChannel.NULL -> BufferStatus.OFFLINE
+                                  else            -> BufferStatus.ONLINE
+                                },
+                                description = topic,
+                                activity = activity,
+                                highlights = highlights,
+                                hiddenState = state
+                              )
+                            }
+                          }
+                        }
+                        BufferInfo.Type.StatusBuffer.toInt()  -> {
+                          network.liveConnectionState.map {
+                            BufferProps(
+                              info = info,
+                              network = network.networkInfo(),
+                              bufferStatus = BufferStatus.OFFLINE,
+                              description = "",
+                              activity = activity,
+                              highlights = highlights,
+                              hiddenState = state
+                            )
+                          }
+                        }
+                        else                                  -> Observable.just(
                           BufferProps(
                             info = info,
                             network = network.networkInfo(),
-                            bufferStatus = when {
-                              user == IrcUser.NULL -> BufferStatus.OFFLINE
-                              away                 -> BufferStatus.AWAY
-                              else                 -> BufferStatus.ONLINE
-                            },
-                            description = realName,
+                            bufferStatus = BufferStatus.OFFLINE,
+                            description = "",
                             activity = activity,
                             highlights = highlights,
                             hiddenState = state
                           )
-                        }
-                      }
-                    }
-                  }
-                  BufferInfo.Type.ChannelBuffer.toInt() -> {
-                    network.liveIrcChannel(
-                      info.bufferName
-                    ).switchMap { channel ->
-                      channel.liveTopic().map { topic ->
-                        BufferProps(
-                          info = info,
-                          network = network.networkInfo(),
-                          bufferStatus = when (channel) {
-                            IrcChannel.NULL -> BufferStatus.OFFLINE
-                            else            -> BufferStatus.ONLINE
-                          },
-                          description = topic,
-                          activity = activity,
-                          highlights = highlights,
-                          hiddenState = state
                         )
                       }
                     }
                   }
-                  BufferInfo.Type.StatusBuffer.toInt()  -> {
-                    network.liveConnectionState.map {
-                      BufferProps(
-                        info = info,
-                        network = network.networkInfo(),
-                        bufferStatus = BufferStatus.OFFLINE,
-                        description = "",
-                        activity = activity,
-                        highlights = highlights,
-                        hiddenState = state
-                      )
-                    }
+
+                bufferSyncer.liveBufferInfos().switchMap {
+                  val buffers = if (showHidden) {
+                    transformIds(ids, BufferHiddenState.VISIBLE) +
+                    transformIds(temp, BufferHiddenState.HIDDEN_TEMPORARY) +
+                    transformIds(perm, BufferHiddenState.HIDDEN_PERMANENT)
+                  } else {
+                    transformIds(ids, BufferHiddenState.VISIBLE)
                   }
-                  else                                  -> Observable.just(
-                    BufferProps(
-                      info = info,
-                      network = network.networkInfo(),
-                      bufferStatus = BufferStatus.OFFLINE,
-                      description = "",
-                      activity = activity,
-                      highlights = highlights,
-                      hiddenState = state
+
+                  combineLatest<BufferProps>(buffers).map { list ->
+                    Pair<BufferViewConfig?, List<BufferProps>>(
+                      config,
+                      list.filter {
+                        (!config.hideInactiveBuffers()) ||
+                        it.bufferStatus != BufferStatus.OFFLINE ||
+                        it.info.type.hasFlag(Buffer_Type.StatusBuffer)
+                      }
                     )
-                  )
+                  }
                 }
               }
             }
-
-          bufferSyncer.liveBufferInfos().switchMap {
-            val buffers = if (showHidden) {
-              transformIds(ids, BufferHiddenState.VISIBLE) +
-              transformIds(temp, BufferHiddenState.HIDDEN_TEMPORARY) +
-              transformIds(perm, BufferHiddenState.HIDDEN_PERMANENT)
-            } else {
-              transformIds(ids, BufferHiddenState.VISIBLE)
-            }
-
-            combineLatest<BufferProps>(buffers).map { list ->
-              Pair<BufferViewConfig?, List<BufferProps>>(
-                config,
-                list.filter {
-                  (!config.hideInactiveBuffers()) ||
-                  it.bufferStatus != BufferStatus.OFFLINE ||
-                  it.info.type.hasFlag(Buffer_Type.StatusBuffer)
-                })
-            }
-          }
+        } else {
+          Observable.just(Pair<BufferViewConfig?, List<BufferProps>>(null, emptyList()))
         }
       }
-    } else {
-      Observable.just(
-        Pair<BufferViewConfig?, List<BufferProps>>(null, emptyList())
-      )
-    }
-  }
-
-  init {
-    showHidden.postValue(false)
-    selectedBufferId.postValue(-1)
-    collapsedNetworks.value = emptySet()
-    recentlySentMessages.value = emptyList()
-  }
 }