diff --git a/build.gradle.kts b/build.gradle.kts
index c8b02b807705e7e1b99554417aff87c3a7890a81..63be78db726b0bface5384110b9cdbca6cc753cd 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -15,4 +15,4 @@ plugins {
 }
 
 group = "de.justjanne.libquassel"
-version = "0.7.0"
+version = "0.8.0"
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandler.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandler.kt
index 1b143193068dd9c08704a4798ed5b955c43a8885..90dadc84583cc6236996aba871bdf8936ee14ef7 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandler.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandler.kt
@@ -12,13 +12,14 @@ package de.justjanne.libquassel.client.session
 import de.justjanne.libquassel.client.util.CoroutineQueue
 import de.justjanne.libquassel.protocol.syncables.ObjectIdentifier
 import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.util.StateHolder
 import de.justjanne.libquassel.protocol.util.update
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 class BaseInitHandler(
   private val session: ClientSession
-) {
+) : StateHolder<BaseInitHandlerState> {
   private val coroutineQueue = CoroutineQueue<Unit>()
 
   fun sync(stub: SyncableStub) {
@@ -45,12 +46,7 @@ class BaseInitHandler(
     coroutineQueue.wait()
   } else Unit
 
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun state(): BaseInitHandlerState = state.value
-
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun flow(): Flow<BaseInitHandlerState> = state
-
-  @PublishedApi
-  internal val state = MutableStateFlow(BaseInitHandlerState())
+  override fun state(): BaseInitHandlerState = state.value
+  override fun flow(): Flow<BaseInitHandlerState> = state
+  private val state = MutableStateFlow(BaseInitHandlerState())
 }
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSession.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSession.kt
index f9d4448fd780b4f05f777ba59fda28dba524d0a5..58d4c6b1b80ea556408ce7499e63c3ca6351d7f3 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSession.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSession.kt
@@ -38,6 +38,7 @@ import de.justjanne.libquassel.protocol.syncables.common.Network
 import de.justjanne.libquassel.protocol.syncables.common.NetworkConfig
 import de.justjanne.libquassel.protocol.syncables.state.CertManagerState
 import de.justjanne.libquassel.protocol.syncables.state.NetworkState
+import de.justjanne.libquassel.protocol.util.StateHolder
 import de.justjanne.libquassel.protocol.util.log.info
 import de.justjanne.libquassel.protocol.util.update
 import de.justjanne.libquassel.protocol.variant.QVariantMap
@@ -51,7 +52,7 @@ class ClientSession(
   protocolFeatures: ProtocolFeatures,
   protocols: List<ProtocolMeta>,
   sslContext: SSLContext
-) : Session {
+) : Session, StateHolder<ClientSessionState> {
   override val side = ProtocolSide.CLIENT
 
   override val rpcHandler = ClientRpcHandler(this)
@@ -202,14 +203,9 @@ class ClientSession(
 
   override val networkConfig get() = state().networkConfig
 
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun state(): ClientSessionState = state.value
-
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun flow(): Flow<ClientSessionState> = state
-
-  @PublishedApi
-  internal val state = MutableStateFlow(
+  override fun state(): ClientSessionState = state.value
+  override fun flow(): Flow<ClientSessionState> = state
+  private val state = MutableStateFlow(
     ClientSessionState(
       networks = mapOf(),
       identities = mapOf(),
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/io/CoroutineChannel.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/io/CoroutineChannel.kt
index 9ac728996772ad3d79f83d6c612a03d78448ab92..22e3de9e52967d03a77fed3500a2d0baa5d17a60 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/io/CoroutineChannel.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/io/CoroutineChannel.kt
@@ -9,6 +9,7 @@
 
 package de.justjanne.libquassel.protocol.io
 
+import de.justjanne.libquassel.protocol.util.StateHolder
 import de.justjanne.libquassel.protocol.util.update
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.asCoroutineDispatcher
@@ -21,7 +22,7 @@ import java.nio.ByteBuffer
 import java.util.concurrent.Executors
 import javax.net.ssl.SSLContext
 
-class CoroutineChannel {
+class CoroutineChannel : StateHolder<CoroutineChannelState> {
   private lateinit var channel: StreamChannel
   private val writeContext = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
   private val readContext = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
@@ -81,12 +82,7 @@ class CoroutineChannel {
     }
   }
 
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun state(): CoroutineChannelState = state.value
-
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun flow(): Flow<CoroutineChannelState> = state
-
-  @PublishedApi
-  internal val state = MutableStateFlow(CoroutineChannelState())
+  override fun state(): CoroutineChannelState = state.value
+  override fun flow(): Flow<CoroutineChannelState> = state
+  private val state = MutableStateFlow(CoroutineChannelState())
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HeartBeatHandler.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HeartBeatHandler.kt
index 5435687b09edfe2570ba9ea004f88c8285b8581b..268c0c5058847c9b3be62a32472b9bb711db0d35 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HeartBeatHandler.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HeartBeatHandler.kt
@@ -9,11 +9,12 @@
 
 package de.justjanne.libquassel.protocol.syncables
 
+import de.justjanne.libquassel.protocol.util.StateHolder
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import org.threeten.bp.Instant
 
-class HeartBeatHandler {
+class HeartBeatHandler : StateHolder<Long?> {
   private var lastReceived: Instant? = null
 
   /**
@@ -30,14 +31,12 @@ class HeartBeatHandler {
   fun recomputeLatency(current: Instant, force: Boolean) {
     val last = lastReceived?.toEpochMilli() ?: return
     val roundtripLatency = current.toEpochMilli() - last
-    if (force || roundtripLatency > this.roundtripLatency.value ?: return) {
+    if (force || roundtripLatency > (this.roundtripLatency.value ?: return)) {
       this.roundtripLatency.value = roundtripLatency
     }
   }
 
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun flow(): Flow<Long?> = roundtripLatency
-
-  @PublishedApi
-  internal val roundtripLatency = MutableStateFlow<Long?>(null)
+  override fun flow(): Flow<Long?> = roundtripLatency
+  override fun state(): Long? = roundtripLatency.value
+  private val roundtripLatency = MutableStateFlow<Long?>(null)
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepository.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepository.kt
index eeab61c0f5e5050585370f7dcbd7790997511611..a783468e48c57d5b0d9f80d4ec4e25aae8057507 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepository.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepository.kt
@@ -9,11 +9,12 @@
 
 package de.justjanne.libquassel.protocol.syncables
 
+import de.justjanne.libquassel.protocol.util.StateHolder
 import de.justjanne.libquassel.protocol.util.update
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class ObjectRepository {
+class ObjectRepository : StateHolder<ObjectRepositoryState> {
   fun add(syncable: SyncableStub): Boolean {
     val identifier = ObjectIdentifier(syncable)
     if (syncable is StatefulSyncableStub) {
@@ -78,12 +79,7 @@ class ObjectRepository {
     return find(T::class.java.simpleName, objectName) as? T
   }
 
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun state() = flow().value
-
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun flow() = state
-
-  @PublishedApi
-  internal val state = MutableStateFlow(ObjectRepositoryState())
+  override fun state() = flow().value
+  override fun flow() = state
+  private val state = MutableStateFlow(ObjectRepositoryState())
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableObject.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableObject.kt
index 0b1067121421a8956a50bca35578e0ebf30ecaee..e3260e3ce920e2bbc24a458c4027d7fc3f713d9d 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableObject.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableObject.kt
@@ -10,6 +10,7 @@
 package de.justjanne.libquassel.protocol.syncables
 
 import de.justjanne.libquassel.protocol.session.Session
+import de.justjanne.libquassel.protocol.util.StateHolder
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -17,7 +18,7 @@ abstract class StatefulSyncableObject<T>(
   session: Session?,
   className: String,
   state: T
-) : SyncableObject(session, className), StatefulSyncableStub {
+) : SyncableObject(session, className), StatefulSyncableStub, StateHolder<T> {
   override fun toString(): String {
     return "$className(objectName=$objectName, state=${state()})"
   }
@@ -38,12 +39,7 @@ abstract class StatefulSyncableObject<T>(
     return result
   }
 
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun state(): T = state.value
-
-  @Suppress("NOTHING_TO_INLINE")
-  inline fun flow(): Flow<T> = state
-
-  @PublishedApi
-  internal val state = MutableStateFlow(state)
+  override fun state(): T = state.value
+  override fun flow(): Flow<T> = state
+  protected val state = MutableStateFlow(state)
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolder.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolder.kt
new file mode 100644
index 0000000000000000000000000000000000000000..03284a967610ea8be93d5315132b6d7e0359b3f3
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolder.kt
@@ -0,0 +1,17 @@
+/*
+ * libquassel
+ * Copyright (c) 2022 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.util
+
+import kotlinx.coroutines.flow.Flow
+
+interface StateHolder<T> {
+  fun state(): T
+  fun flow(): Flow<T>
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolderExtensions.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolderExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..780c6d21428b808c0a0761982e0c7f74bdf1c1f8
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/StateHolderExtensions.kt
@@ -0,0 +1,25 @@
+/*
+ * libquassel
+ * Copyright (c) 2022 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.util
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+
+@ExperimentalCoroutinesApi
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> Flow<StateHolder<T>?>.flatMap(): Flow<T?> =
+  flatMapLatest { it?.flow() ?: emptyFlow() }
+
+@ExperimentalCoroutinesApi
+inline fun <reified T> Flow<Iterable<StateHolder<T>>?>.combineLatest(): Flow<List<T>> =
+  flatMapLatest { combine(it?.map(StateHolder<T>::flow).orEmpty(), ::listOf) }