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 7cc9b63124
commit 7705c93ead
2 changed files with 33 additions and 30 deletions

View file

@ -17,7 +17,6 @@
package io.element.android.features.login.impl.resolver package io.element.android.features.login.impl.resolver
import io.element.android.features.login.impl.resolver.network.WellknownRequest 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.bool.orFalse
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.data.tryOrNull 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.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -42,24 +40,20 @@ class HomeserverResolver @Inject constructor(
private val wellknownRequest: WellknownRequest, 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() val flowContext = currentCoroutineContext()
emit(Async.Uninitialized)
// Debounce
delay(300)
val trimmedUserInput = userInput.trim() val trimmedUserInput = userInput.trim()
if (trimmedUserInput.length < 4) return@flow if (trimmedUserInput.length < 4) return@flow
emit(Async.Loading())
val candidateBase = trimmedUserInput.ensureProtocol().removeSuffix("/") val candidateBase = trimmedUserInput.ensureProtocol().removeSuffix("/")
val list = getUrlCandidates(candidateBase) val list = getUrlCandidates(candidateBase)
val currentList = Collections.synchronizedList(mutableListOf<HomeserverData>()) val currentList = Collections.synchronizedList(mutableListOf<HomeserverData>())
// Run all the requests in parallel // Run all the requests in parallel
withContext(dispatchers.io) { withContext(dispatchers.io) {
list.map { list.map { url ->
async { async {
val wellKnown = tryOrNull { val wellKnown = tryOrNull {
withTimeout(5000) { withTimeout(5000) {
wellknownRequest.execute(it) wellknownRequest.execute(url)
} }
} }
val isValid = wellKnown?.isValid().orFalse() val isValid = wellKnown?.isValid().orFalse()
@ -68,35 +62,29 @@ class HomeserverResolver @Inject constructor(
// Emit the list as soon as possible // Emit the list as soon as possible
currentList.add( currentList.add(
HomeserverData( HomeserverData(
homeserverUrl = it, homeserverUrl = url,
isWellknownValid = true, isWellknownValid = true,
supportSlidingSync = supportSlidingSync supportSlidingSync = supportSlidingSync
) )
) )
withContext(flowContext) { withContext(flowContext) {
emit(Async.Success(currentList)) emit(currentList.toList())
} }
} }
} }
}.awaitAll() }.awaitAll()
} }
// If list is empty, and the user has entered an URL, do not block the user. // If list is empty, and the user has entered an URL, do not block the user.
if (currentList.isEmpty()) { if (currentList.isEmpty() && trimmedUserInput.isValidUrl()) {
if (trimmedUserInput.isValidUrl()) { emit(
emit( listOf(
Async.Success( HomeserverData(
listOf( homeserverUrl = trimmedUserInput,
HomeserverData( isWellknownValid = false,
homeserverUrl = trimmedUserInput, supportSlidingSync = false,
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.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember 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.features.login.impl.resolver.HomeserverResolver
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class SearchAccountProviderPresenter @Inject constructor( class SearchAccountProviderPresenter @Inject constructor(
@ -42,14 +46,12 @@ class SearchAccountProviderPresenter @Inject constructor(
} }
val changeServerState = changeServerPresenter.present() val changeServerState = changeServerPresenter.present()
var data: Async<List<HomeserverData>> by remember { val data: MutableState<Async<List<HomeserverData>>> = remember {
mutableStateOf(Async.Uninitialized) mutableStateOf(Async.Uninitialized)
} }
LaunchedEffect(userInput) { LaunchedEffect(userInput) {
homeserverResolver.resolve(userInput).collect { onUserInput(userInput, data)
data = it
}
} }
fun handleEvents(event: SearchAccountProviderEvents) { fun handleEvents(event: SearchAccountProviderEvents) {
@ -62,9 +64,22 @@ class SearchAccountProviderPresenter @Inject constructor(
return SearchAccountProviderState( return SearchAccountProviderState(
userInput = userInput, userInput = userInput,
userInputResult = data, userInputResult = data.value,
changeServerState = changeServerState, changeServerState = changeServerState,
eventSink = ::handleEvents 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
}
}
} }