Let HomeserverResolver emit the List and not the Async.

This commit is contained in:
Benoit Marty 2023-06-12 18:28:57 +02:00
parent f7fbab6ea7
commit f7b20e2373
2 changed files with 33 additions and 30 deletions

View file

@ -17,7 +17,6 @@
package io.element.android.features.login.impl.resolver
import io.element.android.features.login.impl.resolver.network.WellknownRequest
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.data.tryOrNull
@ -26,7 +25,6 @@ import io.element.android.libraries.core.uri.isValidUrl
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
@ -42,24 +40,20 @@ class HomeserverResolver @Inject constructor(
private val wellknownRequest: WellknownRequest,
) {
suspend fun resolve(userInput: String): Flow<Async<List<HomeserverData>>> = flow {
suspend fun resolve(userInput: String): Flow<List<HomeserverData>> = flow {
val flowContext = currentCoroutineContext()
emit(Async.Uninitialized)
// Debounce
delay(300)
val trimmedUserInput = userInput.trim()
if (trimmedUserInput.length < 4) return@flow
emit(Async.Loading())
val candidateBase = trimmedUserInput.ensureProtocol().removeSuffix("/")
val list = getUrlCandidates(candidateBase)
val currentList = Collections.synchronizedList(mutableListOf<HomeserverData>())
// Run all the requests in parallel
withContext(dispatchers.io) {
list.map {
list.map { url ->
async {
val wellKnown = tryOrNull {
withTimeout(5000) {
wellknownRequest.execute(it)
wellknownRequest.execute(url)
}
}
val isValid = wellKnown?.isValid().orFalse()
@ -68,35 +62,29 @@ class HomeserverResolver @Inject constructor(
// Emit the list as soon as possible
currentList.add(
HomeserverData(
homeserverUrl = it,
homeserverUrl = url,
isWellknownValid = true,
supportSlidingSync = supportSlidingSync
)
)
withContext(flowContext) {
emit(Async.Success(currentList))
emit(currentList.toList())
}
}
}
}.awaitAll()
}
// If list is empty, and the user has entered an URL, do not block the user.
if (currentList.isEmpty()) {
if (trimmedUserInput.isValidUrl()) {
emit(
Async.Success(
listOf(
HomeserverData(
homeserverUrl = trimmedUserInput,
isWellknownValid = false,
supportSlidingSync = false,
)
)
if (currentList.isEmpty() && trimmedUserInput.isValidUrl()) {
emit(
listOf(
HomeserverData(
homeserverUrl = trimmedUserInput,
isWellknownValid = false,
supportSlidingSync = false,
)
)
} else {
emit(Async.Uninitialized)
}
)
}
}

View file

@ -18,6 +18,7 @@ package io.element.android.features.login.impl.screens.searchaccountprovider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -28,6 +29,9 @@ import io.element.android.features.login.impl.resolver.HomeserverData
import io.element.android.features.login.impl.resolver.HomeserverResolver
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject
class SearchAccountProviderPresenter @Inject constructor(
@ -42,14 +46,12 @@ class SearchAccountProviderPresenter @Inject constructor(
}
val changeServerState = changeServerPresenter.present()
var data: Async<List<HomeserverData>> by remember {
val data: MutableState<Async<List<HomeserverData>>> = remember {
mutableStateOf(Async.Uninitialized)
}
LaunchedEffect(userInput) {
homeserverResolver.resolve(userInput).collect {
data = it
}
onUserInput(userInput, data)
}
fun handleEvents(event: SearchAccountProviderEvents) {
@ -62,9 +64,22 @@ class SearchAccountProviderPresenter @Inject constructor(
return SearchAccountProviderState(
userInput = userInput,
userInputResult = data,
userInputResult = data.value,
changeServerState = changeServerState,
eventSink = ::handleEvents
)
}
private fun CoroutineScope.onUserInput(userInput: String, data: MutableState<Async<List<HomeserverData>>>) = launch {
data.value = Async.Uninitialized
// Debounce
delay(300)
data.value = Async.Loading()
homeserverResolver.resolve(userInput).collect {
data.value = Async.Success(it)
}
if (data.value !is Async.Success) {
data.value = Async.Uninitialized
}
}
}