From c7efd1feb99179585e865affa866d680a96fefe2 Mon Sep 17 00:00:00 2001
From: aqulu <dev@aqu.lu>
Date: Tue, 8 Dec 2020 19:04:30 +0900
Subject: [PATCH 1/6] Convert StateService to suspend functions

Signed-off-by: aqulu <dev@aqu.lu>
---
 CHANGES.md                                    |   2 +-
 matrix-sdk-android-rx/build.gradle            |   1 +
 .../java/org/matrix/android/sdk/rx/RxRoom.kt  |  35 +++---
 .../api/session/room/state/StateService.kt    |  16 +--
 .../session/room/state/DefaultStateService.kt | 110 +++++++-----------
 .../home/room/detail/RoomDetailViewModel.kt   |  25 ++--
 .../RoomMemberProfileViewModel.kt             |   4 +-
 .../roomprofile/alias/RoomAliasViewModel.kt   |  11 +-
 .../members/RoomMemberListViewModel.kt        |   3 +-
 .../features/widgets/WidgetPostAPIHandler.kt  |  39 +++++--
 10 files changed, 124 insertions(+), 122 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index b16a6690bc..b8ed81efb7 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -17,7 +17,7 @@ Translations 🗣:
  -
 
 SDK API changes ⚠️:
- -
+ - StateService now exposes suspendable function instead of using MatrixCallback.
 
 Build 🧱:
  - Upgrade some dependencies and Kotlin version
diff --git a/matrix-sdk-android-rx/build.gradle b/matrix-sdk-android-rx/build.gradle
index 37f41d0a2a..a99b5856ba 100644
--- a/matrix-sdk-android-rx/build.gradle
+++ b/matrix-sdk-android-rx/build.gradle
@@ -38,6 +38,7 @@ dependencies {
     implementation 'androidx.appcompat:appcompat:1.2.0'
     implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
     implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
+    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlin_coroutines_version"
 
     // Paging
     implementation "androidx.paging:paging-runtime-ktx:2.1.2"
diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt
index bf4bcacc31..b938f60e39 100644
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt
+++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt
@@ -17,14 +17,20 @@
 package org.matrix.android.sdk.rx
 
 import android.net.Uri
+import io.reactivex.Completable
+import io.reactivex.Observable
+import io.reactivex.Single
+import kotlinx.coroutines.rx2.rxCompletable
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.identity.ThreePid
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams
 import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
+import org.matrix.android.sdk.api.session.room.model.GuestAccess
 import org.matrix.android.sdk.api.session.room.model.ReadReceipt
 import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
@@ -32,11 +38,6 @@ import org.matrix.android.sdk.api.session.room.send.UserDraft
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
-import io.reactivex.Completable
-import io.reactivex.Observable
-import io.reactivex.Single
-import org.matrix.android.sdk.api.session.room.model.GuestAccess
-import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
 
 class RxRoom(private val room: Room) {
 
@@ -121,28 +122,28 @@ class RxRoom(private val room: Room) {
         room.invite3pid(threePid, it)
     }
 
-    fun updateTopic(topic: String): Completable = completableBuilder<Unit> {
-        room.updateTopic(topic, it)
+    fun updateTopic(topic: String): Completable = rxCompletable {
+        room.updateTopic(topic)
     }
 
-    fun updateName(name: String): Completable = completableBuilder<Unit> {
-        room.updateName(name, it)
+    fun updateName(name: String): Completable = rxCompletable {
+        room.updateName(name)
     }
 
-    fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = completableBuilder<Unit> {
-        room.updateHistoryReadability(readability, it)
+    fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = rxCompletable {
+        room.updateHistoryReadability(readability)
     }
 
-    fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?): Completable = completableBuilder<Unit> {
-        room.updateJoinRule(joinRules, guestAccess, it)
+    fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?): Completable = rxCompletable {
+        room.updateJoinRule(joinRules, guestAccess)
     }
 
-    fun updateAvatar(avatarUri: Uri, fileName: String): Completable = completableBuilder<Unit> {
-        room.updateAvatar(avatarUri, fileName, it)
+    fun updateAvatar(avatarUri: Uri, fileName: String): Completable = rxCompletable {
+        room.updateAvatar(avatarUri, fileName)
     }
 
-    fun deleteAvatar(): Completable = completableBuilder<Unit> {
-        room.deleteAvatar(it)
+    fun deleteAvatar(): Completable = rxCompletable {
+        room.deleteAvatar()
     }
 }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
index 74e3faf38a..98dde5839f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
@@ -33,41 +33,41 @@ interface StateService {
     /**
      * Update the topic of the room
      */
-    fun updateTopic(topic: String, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun updateTopic(topic: String)
 
     /**
      * Update the name of the room
      */
-    fun updateName(name: String, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun updateName(name: String)
 
     /**
      * Update the canonical alias of the room
      * @param alias the canonical alias, or null to reset the canonical alias of this room
      * @param altAliases the alternative aliases for this room. It should include the canonical alias if any.
      */
-    fun updateCanonicalAlias(alias: String?, altAliases: List<String>, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun updateCanonicalAlias(alias: String?, altAliases: List<String>)
 
     /**
      * Update the history readability of the room
      */
-    fun updateHistoryReadability(readability: RoomHistoryVisibility, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun updateHistoryReadability(readability: RoomHistoryVisibility)
 
     /**
      * Update the join rule and/or the guest access
      */
-    fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?)
 
     /**
      * Update the avatar of the room
      */
-    fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun updateAvatar(avatarUri: Uri, fileName: String)
 
     /**
      * Delete the avatar of the room
      */
-    fun deleteAvatar(callback: MatrixCallback<Unit>): Cancelable
+    suspend fun deleteAvatar()
 
-    fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict)
 
     fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
index 6015d945c4..607784b48f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
@@ -20,7 +20,7 @@ import android.net.Uri
 import androidx.lifecycle.LiveData
 import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
-import org.matrix.android.sdk.api.MatrixCallback
+import kotlinx.coroutines.withContext
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -32,20 +32,14 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
 import org.matrix.android.sdk.api.session.room.state.StateService
-import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.session.content.FileUploader
 import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask
-import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.task.configureWith
-import org.matrix.android.sdk.internal.task.launchToCallback
 import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
-import org.matrix.android.sdk.internal.util.awaitCallback
 
 internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
                                                                private val stateEventDataSource: StateEventDataSource,
-                                                               private val taskExecutor: TaskExecutor,
                                                                private val sendStateTask: SendStateTask,
                                                                private val coroutineDispatchers: MatrixCoroutineDispatchers,
                                                                private val fileUploader: FileUploader,
@@ -73,45 +67,41 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
         return stateEventDataSource.getStateEventsLive(roomId, eventTypes, stateKey)
     }
 
-    override fun sendStateEvent(
+    override suspend fun sendStateEvent(
             eventType: String,
             stateKey: String?,
-            body: JsonDict,
-            callback: MatrixCallback<Unit>
-    ): Cancelable {
-        val params = SendStateTask.Params(
-                roomId = roomId,
-                stateKey = stateKey,
-                eventType = eventType,
-                body = body
-        )
-        return sendStateTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+            body: JsonDict
+    ) {
+        withContext(coroutineDispatchers.main) {
+            val params = SendStateTask.Params(
+                    roomId = roomId,
+                    stateKey = stateKey,
+                    eventType = eventType,
+                    body = body
+            )
+
+            sendStateTask.execute(params)
+        }
     }
 
-    override fun updateTopic(topic: String, callback: MatrixCallback<Unit>): Cancelable {
-        return sendStateEvent(
+    override suspend fun updateTopic(topic: String) {
+        sendStateEvent(
                 eventType = EventType.STATE_ROOM_TOPIC,
                 body = mapOf("topic" to topic),
-                callback = callback,
                 stateKey = null
         )
     }
 
-    override fun updateName(name: String, callback: MatrixCallback<Unit>): Cancelable {
-        return sendStateEvent(
+    override suspend fun updateName(name: String) {
+        sendStateEvent(
                 eventType = EventType.STATE_ROOM_NAME,
                 body = mapOf("name" to name),
-                callback = callback,
                 stateKey = null
         )
     }
 
-    override fun updateCanonicalAlias(alias: String?, altAliases: List<String>, callback: MatrixCallback<Unit>): Cancelable {
-        return sendStateEvent(
+    override suspend fun updateCanonicalAlias(alias: String?, altAliases: List<String>) {
+        sendStateEvent(
                 eventType = EventType.STATE_ROOM_CANONICAL_ALIAS,
                 body = RoomCanonicalAliasContent(
                         canonicalAlias = alias,
@@ -123,64 +113,52 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
                                 // Sort for the cleanup
                                 .sorted()
                 ).toContent(),
-                callback = callback,
                 stateKey = null
         )
     }
 
-    override fun updateHistoryReadability(readability: RoomHistoryVisibility, callback: MatrixCallback<Unit>): Cancelable {
-        return sendStateEvent(
+    override suspend fun updateHistoryReadability(readability: RoomHistoryVisibility) {
+        sendStateEvent(
                 eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY,
                 body = mapOf("history_visibility" to readability),
-                callback = callback,
                 stateKey = null
         )
     }
 
-    override fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, callback: MatrixCallback<Unit>): Cancelable {
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+    override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) {
+        withContext(coroutineDispatchers.main) {
             if (joinRules != null) {
-                awaitCallback<Unit> {
-                    sendStateEvent(
-                            eventType = EventType.STATE_ROOM_JOIN_RULES,
-                            body = RoomJoinRulesContent(joinRules).toContent(),
-                            callback = it,
-                            stateKey = null
-                    )
-                }
+                sendStateEvent(
+                        eventType = EventType.STATE_ROOM_JOIN_RULES,
+                        body = RoomJoinRulesContent(joinRules).toContent(),
+                        stateKey = null
+                )
             }
             if (guestAccess != null) {
-                awaitCallback<Unit> {
-                    sendStateEvent(
-                            eventType = EventType.STATE_ROOM_GUEST_ACCESS,
-                            body = RoomGuestAccessContent(guestAccess).toContent(),
-                            callback = it,
-                            stateKey = null
-                    )
-                }
-            }
-        }
-    }
-
-    override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback<Unit>): Cancelable {
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
-            val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg")
-            awaitCallback<Unit> {
                 sendStateEvent(
-                        eventType = EventType.STATE_ROOM_AVATAR,
-                        body = mapOf("url" to response.contentUri),
-                        callback = it,
+                        eventType = EventType.STATE_ROOM_GUEST_ACCESS,
+                        body = RoomGuestAccessContent(guestAccess).toContent(),
                         stateKey = null
                 )
             }
         }
     }
 
-    override fun deleteAvatar(callback: MatrixCallback<Unit>): Cancelable {
-        return sendStateEvent(
+    override suspend fun updateAvatar(avatarUri: Uri, fileName: String) {
+        withContext(coroutineDispatchers.main) {
+            val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg")
+            sendStateEvent(
+                    eventType = EventType.STATE_ROOM_AVATAR,
+                    body = mapOf("url" to response.contentUri),
+                    stateKey = null
+            )
+        }
+    }
+
+    override suspend fun deleteAvatar() {
+        sendStateEvent(
                 eventType = EventType.STATE_ROOM_AVATAR,
                 body = emptyMap(),
-                callback = callback,
                 stateKey = null
         )
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index a83dddc9ac..5e414422f7 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -292,9 +292,7 @@ class RoomDetailViewModel @AssistedInject constructor(
     private fun handleSetNewAvatar(action: RoomDetailAction.SetAvatarAction) {
         viewModelScope.launch(Dispatchers.IO) {
             try {
-                awaitCallback<Unit> {
-                    room.updateAvatar(action.newAvatarUri, action.newAvatarFileName, it)
-                }
+                room.updateAvatar(action.newAvatarUri, action.newAvatarFileName)
                 _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
             } catch (failure: Throwable) {
                 _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
@@ -854,8 +852,8 @@ class RoomDetailViewModel @AssistedInject constructor(
     }
 
     private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
-        launchSlashCommandFlow {
-            room.updateTopic(changeTopic.topic, it)
+        launchSlashCommandFlowSuspendable {
+            room.updateTopic(changeTopic.topic)
         }
     }
 
@@ -876,9 +874,9 @@ class RoomDetailViewModel @AssistedInject constructor(
                 ?.content
                 ?.toModel<PowerLevelsContent>() ?: return
 
-        launchSlashCommandFlow {
+        launchSlashCommandFlowSuspendable {
             currentPowerLevelsContent.setUserPowerLevel(setUserPowerLevel.userId, setUserPowerLevel.powerLevel)
-            room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, currentPowerLevelsContent.toContent(), it)
+            room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, currentPowerLevelsContent.toContent())
         }
     }
 
@@ -920,6 +918,19 @@ class RoomDetailViewModel @AssistedInject constructor(
         lambda.invoke(matrixCallback)
     }
 
+    private fun launchSlashCommandFlowSuspendable(block: suspend () -> Unit) {
+        _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
+        viewModelScope.launch {
+            val event = try {
+                block()
+                RoomDetailViewEvents.SlashCommandResultOk
+            } catch (failure: Exception) {
+                RoomDetailViewEvents.SlashCommandResultError(failure)
+            }
+            _viewEvents.post(event)
+        }
+    }
+
     private fun handleSendReaction(action: RoomDetailAction.SendReaction) {
         room.sendReaction(action.targetEventId, action.reaction)
     }
diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt
index 78562ea351..39b5884308 100644
--- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt
@@ -166,9 +166,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
             viewModelScope.launch {
                 _viewEvents.post(RoomMemberProfileViewEvents.Loading())
                 try {
-                    awaitCallback<Unit> {
-                        room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, currentPowerLevelsContent.toContent(), it)
-                    }
+                    room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, currentPowerLevelsContent.toContent())
                     _viewEvents.post(RoomMemberProfileViewEvents.OnSetPowerLevelSuccess)
                 } catch (failure: Throwable) {
                     _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure))
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt
index 5873d9ce8a..af0972913a 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt
@@ -301,21 +301,20 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo
 
     private fun updateCanonicalAlias(canonicalAlias: String?, alternativeAliases: List<String>, closeForm: Boolean) {
         postLoading(true)
-        room.updateCanonicalAlias(canonicalAlias, alternativeAliases, object : MatrixCallback<Unit> {
-            override fun onSuccess(data: Unit) {
+        viewModelScope.launch {
+            try {
+                room.updateCanonicalAlias(canonicalAlias, alternativeAliases)
                 setState {
                     copy(
                             isLoading = false,
                             publishManuallyState = if (closeForm) RoomAliasViewState.AddAliasState.Closed else publishManuallyState
                     )
                 }
-            }
-
-            override fun onFailure(failure: Throwable) {
+            } catch (failure: Throwable) {
                 postLoading(false)
                 _viewEvents.post(RoomAliasViewEvents.Failure(failure))
             }
-        })
+        }
     }
 
     private fun handleAddLocalAlias() = withState { state ->
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
index 9e402c675b..fe8ed63cce 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
@@ -197,8 +197,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
             room.sendStateEvent(
                     eventType = EventType.STATE_ROOM_THIRD_PARTY_INVITE,
                     stateKey = action.stateKey,
-                    body = emptyMap(),
-                    callback = NoOpMatrixCallback()
+                    body = emptyMap()
             )
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt
index a4d759250d..fbd08b0c9f 100644
--- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt
@@ -21,6 +21,8 @@ import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
 import im.vector.app.R
 import im.vector.app.core.resources.StringProvider
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
@@ -310,12 +312,19 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
 
         val params = HashMap<String, Any>()
         params["status"] = status
-        room.sendStateEvent(
-                eventType = EventType.PLUMBING,
-                stateKey = null,
-                body = params,
-                callback = createWidgetAPICallback(widgetPostAPIMediator, eventData)
-        )
+
+        GlobalScope.launch {
+            try {
+                room.sendStateEvent(
+                        eventType = EventType.PLUMBING,
+                        stateKey = null,
+                        body = params
+                )
+                widgetPostAPIMediator.sendSuccess(eventData)
+            } catch (failure: Exception) {
+                widgetPostAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_failed_to_send_request), eventData)
+            }
+        }
     }
 
     /**
@@ -333,12 +342,18 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
         Timber.d(description)
         val content = eventData["content"] as JsonDict
         val stateKey = "_$userId"
-        room.sendStateEvent(
-                eventType = EventType.BOT_OPTIONS,
-                stateKey = stateKey,
-                body = content,
-                callback = createWidgetAPICallback(widgetPostAPIMediator, eventData)
-        )
+        GlobalScope.launch {
+            try {
+                room.sendStateEvent(
+                        eventType = EventType.BOT_OPTIONS,
+                        stateKey = stateKey,
+                        body = content
+                )
+                widgetPostAPIMediator.sendSuccess(eventData)
+            } catch (failure: Exception) {
+                widgetPostAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_failed_to_send_request), eventData)
+            }
+        }
     }
 
     /**

From 416f57b1d791e71722d0847ba4549c328e0bab5e Mon Sep 17 00:00:00 2001
From: aqulu <dev@aqu.lu>
Date: Tue, 8 Dec 2020 22:02:00 +0900
Subject: [PATCH 2/6] Fix failing test compilation

Signed-off-by: aqulu <dev@aqu.lu>
---
 .../crypto/encryption/EncryptionTest.kt         | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
index e42059c639..da5e90abdd 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
@@ -17,13 +17,13 @@
 package org.matrix.android.sdk.internal.crypto.encryption
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlinx.coroutines.runBlocking
 import org.amshove.kluent.shouldBe
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
-import org.matrix.android.sdk.api.NoOpMatrixCallback
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.api.session.room.Room
@@ -57,13 +57,14 @@ class EncryptionTest : InstrumentedTest {
     @Test
     fun test_EncryptionStateEvent() {
         performTest(roomShouldBeEncrypted = true) { room ->
-            // Send an encryption Event as a State Event
-            room.sendStateEvent(
-                    eventType = EventType.STATE_ROOM_ENCRYPTION,
-                    stateKey = null,
-                    body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent(),
-                    callback = NoOpMatrixCallback()
-            )
+            runBlocking {
+                // Send an encryption Event as a State Event
+                room.sendStateEvent(
+                        eventType = EventType.STATE_ROOM_ENCRYPTION,
+                        stateKey = null,
+                        body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
+                )
+            }
         }
     }
 

From c889deaab1dccd2207612a00b2c9b0112a4bb6f5 Mon Sep 17 00:00:00 2001
From: aqulu <dev@aqu.lu>
Date: Tue, 8 Dec 2020 22:02:49 +0900
Subject: [PATCH 3/6] Remove unused imports

Signed-off-by: aqulu <dev@aqu.lu>
---
 .../matrix/android/sdk/api/session/room/state/StateService.kt   | 2 --
 .../vector/app/features/roomprofile/alias/RoomAliasViewModel.kt | 1 -
 .../app/features/roomprofile/members/RoomMemberListViewModel.kt | 1 -
 3 files changed, 4 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
index 98dde5839f..444366e912 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
@@ -18,13 +18,11 @@ package org.matrix.android.sdk.api.session.room.state
 
 import android.net.Uri
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.model.GuestAccess
 import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
-import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.Optional
 
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt
index af0972913a..f470eeefc2 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt
@@ -30,7 +30,6 @@ import im.vector.app.core.extensions.exhaustive
 import im.vector.app.core.platform.VectorViewModel
 import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
 import kotlinx.coroutines.launch
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.events.model.EventType
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
index fe8ed63cce..9f15e62b3b 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
@@ -30,7 +30,6 @@ import io.reactivex.Observable
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.functions.BiFunction
 import kotlinx.coroutines.launch
-import org.matrix.android.sdk.api.NoOpMatrixCallback
 import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.query.QueryStringValue

From 19d421df84485effcdc6d5f3ec9fc018f5185f35 Mon Sep 17 00:00:00 2001
From: aqulu <dev@aqu.lu>
Date: Tue, 8 Dec 2020 22:06:21 +0900
Subject: [PATCH 4/6] Remove coroutine context change for sendStateEvent

Signed-off-by: aqulu <dev@aqu.lu>
---
 .../session/room/state/DefaultStateService.kt   | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
index 607784b48f..f71b8868ed 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
@@ -72,16 +72,13 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
             stateKey: String?,
             body: JsonDict
     ) {
-        withContext(coroutineDispatchers.main) {
-            val params = SendStateTask.Params(
-                    roomId = roomId,
-                    stateKey = stateKey,
-                    eventType = eventType,
-                    body = body
-            )
-
-            sendStateTask.execute(params)
-        }
+        val params = SendStateTask.Params(
+                roomId = roomId,
+                stateKey = stateKey,
+                eventType = eventType,
+                body = body
+        )
+        sendStateTask.execute(params)
     }
 
     override suspend fun updateTopic(topic: String) {

From 40b9f031325cac0a7474a3706341318e62d48dc4 Mon Sep 17 00:00:00 2001
From: aqulu <dev@aqu.lu>
Date: Tue, 8 Dec 2020 22:11:35 +0900
Subject: [PATCH 5/6] Remove explicit coroutine context changes

Signed-off-by: aqulu <dev@aqu.lu>
---
 .../session/room/state/DefaultStateService.kt | 45 ++++++++-----------
 1 file changed, 19 insertions(+), 26 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
index f71b8868ed..78663e8ce2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
@@ -20,7 +20,6 @@ import android.net.Uri
 import androidx.lifecycle.LiveData
 import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
-import kotlinx.coroutines.withContext
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -36,12 +35,10 @@ import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.session.content.FileUploader
 import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask
-import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
 
 internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
                                                                private val stateEventDataSource: StateEventDataSource,
                                                                private val sendStateTask: SendStateTask,
-                                                               private val coroutineDispatchers: MatrixCoroutineDispatchers,
                                                                private val fileUploader: FileUploader,
                                                                private val addRoomAliasTask: AddRoomAliasTask
 ) : StateService {
@@ -123,33 +120,29 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
     }
 
     override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) {
-        withContext(coroutineDispatchers.main) {
-            if (joinRules != null) {
-                sendStateEvent(
-                        eventType = EventType.STATE_ROOM_JOIN_RULES,
-                        body = RoomJoinRulesContent(joinRules).toContent(),
-                        stateKey = null
-                )
-            }
-            if (guestAccess != null) {
-                sendStateEvent(
-                        eventType = EventType.STATE_ROOM_GUEST_ACCESS,
-                        body = RoomGuestAccessContent(guestAccess).toContent(),
-                        stateKey = null
-                )
-            }
+        if (joinRules != null) {
+            sendStateEvent(
+                    eventType = EventType.STATE_ROOM_JOIN_RULES,
+                    body = RoomJoinRulesContent(joinRules).toContent(),
+                    stateKey = null
+            )
+        }
+        if (guestAccess != null) {
+            sendStateEvent(
+                    eventType = EventType.STATE_ROOM_GUEST_ACCESS,
+                    body = RoomGuestAccessContent(guestAccess).toContent(),
+                    stateKey = null
+            )
         }
     }
 
     override suspend fun updateAvatar(avatarUri: Uri, fileName: String) {
-        withContext(coroutineDispatchers.main) {
-            val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg")
-            sendStateEvent(
-                    eventType = EventType.STATE_ROOM_AVATAR,
-                    body = mapOf("url" to response.contentUri),
-                    stateKey = null
-            )
-        }
+        val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg")
+        sendStateEvent(
+                eventType = EventType.STATE_ROOM_AVATAR,
+                body = mapOf("url" to response.contentUri),
+                stateKey = null
+        )
     }
 
     override suspend fun deleteAvatar() {

From ed822becc68c16e03330b0139a7287861ccf72de Mon Sep 17 00:00:00 2001
From: aqulu <dev@aqu.lu>
Date: Wed, 9 Dec 2020 08:39:00 +0900
Subject: [PATCH 6/6] Fix try-catch behavior of sendStateEvent actions

Signed-off-by: aqulu <dev@aqu.lu>
---
 .../features/widgets/WidgetPostAPIHandler.kt  | 52 +++++++++++--------
 1 file changed, 29 insertions(+), 23 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt
index fbd08b0c9f..3906ea687c 100644
--- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt
@@ -22,6 +22,7 @@ import com.squareup.inject.assisted.AssistedInject
 import im.vector.app.R
 import im.vector.app.core.resources.StringProvider
 import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.Session
@@ -312,18 +313,12 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
 
         val params = HashMap<String, Any>()
         params["status"] = status
-
-        GlobalScope.launch {
-            try {
-                room.sendStateEvent(
-                        eventType = EventType.PLUMBING,
-                        stateKey = null,
-                        body = params
-                )
-                widgetPostAPIMediator.sendSuccess(eventData)
-            } catch (failure: Exception) {
-                widgetPostAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_failed_to_send_request), eventData)
-            }
+        launchWidgetAPIAction(widgetPostAPIMediator, eventData) {
+            room.sendStateEvent(
+                    eventType = EventType.PLUMBING,
+                    stateKey = null,
+                    body = params
+            )
         }
     }
 
@@ -342,17 +337,13 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
         Timber.d(description)
         val content = eventData["content"] as JsonDict
         val stateKey = "_$userId"
-        GlobalScope.launch {
-            try {
-                room.sendStateEvent(
-                        eventType = EventType.BOT_OPTIONS,
-                        stateKey = stateKey,
-                        body = content
-                )
-                widgetPostAPIMediator.sendSuccess(eventData)
-            } catch (failure: Exception) {
-                widgetPostAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_failed_to_send_request), eventData)
-            }
+
+        launchWidgetAPIAction(widgetPostAPIMediator, eventData) {
+            room.sendStateEvent(
+                    eventType = EventType.BOT_OPTIONS,
+                    stateKey = stateKey,
+                    body = content
+            )
         }
     }
 
@@ -471,4 +462,19 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
     private fun createWidgetAPICallback(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict): WidgetAPICallback {
         return WidgetAPICallback(widgetPostAPIMediator, eventData, stringProvider)
     }
+
+    private fun launchWidgetAPIAction(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict, block: suspend () -> Unit): Job {
+        return GlobalScope.launch {
+            kotlin.runCatching {
+                block()
+            }.fold(
+                    onSuccess = {
+                        widgetPostAPIMediator.sendSuccess(eventData)
+                    },
+                    onFailure = {
+                        widgetPostAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_failed_to_send_request), eventData)
+                    }
+            )
+        }
+    }
 }