提问人:Jay 提问时间:11/15/2023 最后编辑:Jay 更新时间:11/15/2023 访问量:38
stateIn 无法收集 Result.Success 状态
stateIn not able to collect the Result.Success state
问:
我是 Kotlin Flow 和 StateFlow 的绝对新手。我无法弄清楚为什么对于其中一个 REST API,即使 API 成功,Flow 也不会发出 Success 结果(我可以看到 HTTP 日志记录拦截器打印带有有效响应正文的 200 状态代码)。
我的应用程序有 2 个 REST API,这些 API 正在被调用:ServiceViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.domain.usecase.GetServiceTypesUseCase
import com.example.domain.usecase.GetServicesUseCase
import com.example.ui.util.Result
import com.example.ui.util.asResult
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
class ServiceViewModel(
private val getServicesUseCase: GetServicesUseCase,
private val getServiceTypesUseCase: GetServiceTypesUseCase,
) : ViewModel() {
val serviceTypeUiState = getServiceTypesUseCase()
.asResult()
.map { it: Result<List<FilterableServiceType>> ->
// This never receives the Success emission from asResult() call; only Loading
println("Result state is : $it")
when (it) {
is Result.Error -> ServiceTypesUiState.Error(it.exception)
is Result.Loading -> ServiceTypesUiState.Loading
is Result.Success -> ServiceTypesUiState.Success(it.data)
}
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = Result.Loading
)
val servicesUiState = getServicesUseCase()
.asResult()
.map {
when (it) {
is Result.Error -> ServicesUiState.Error(it.exception)
is Result.Loading -> ServicesUiState.Loading
is Result.Success -> ServicesUiState.Success(it.data)
}
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = Result.Loading
)
}
结果.kt
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
sealed interface Result<out T> {
data class Success<T>(val data: T) : Result<T>
data class Error(val exception: Throwable) : Result<Nothing>
data object Loading : Result<Nothing>
}
fun <T> Flow<T>.asResult(): Flow<Result<T>> {
return this
.map<T, Result<T>> {
Result.Success(data = it)
}
.onStart {
emit(Result.Loading)
}
.catch {
emit(Result.Error(exception = it))
}
}
以下是从域和存储库层获取数据的 2 个用例:
import com.example.domain.ServiceRepository
import com.example.domain.model.Service
import com.example.domain.util.DataStoreManager
import com.example.domain.model.FilterableServiceType
import kotlinx.coroutines.flow.Flow
class GetServicesUseCase(
private val serviceRepository: ServiceRepository,
private val dataStoreManager: DataStoreManager,
) {
operator fun invoke(): Flow<List<Service>> =
serviceRepository.getServices(dataStoreManager.getUserToken())
}
class GetServiceTypesUseCase(
private val serviceRepository: ServiceRepository,
private val dataStoreManager: DataStoreManager,
) {
operator fun invoke(): Flow<List<FilterableServiceType>> =
serviceRepository.getServiceTypes(
tokenFlow = dataStoreManager.getUserToken()
)
}
ServiceRepositoryImpl.kt:
import com.example.domain.ServiceRepository
import com.example.domain.model.FilterableServiceType
import com.example.domain.model.Service
import com.example.repository.api.model.ServiceType
import com.example.repository.api.model.toResponse
import com.example.repository.api.model.toService
import com.example.repository.util.toBearerToken
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
class ServiceRepositoryImpl(private val apiDataSource: ApiDataSource) : ServiceRepository {
override fun getServices(tokenFlow: Flow<String>): Flow<List<Service>> {
return flow {
val token = tokenFlow.first().toString().toBearerToken()
val services = apiDataSource.getServices(token).map {
it.toService()
}
emit(services)
}
}
override fun getServiceTypes(tokenFlow: Flow<String>): Flow<List<FilterableServiceType>> =
flow {
val token = tokenFlow.first().toString().toBearerToken()
apiDataSource.getServiceTypes(token)
.map(ServiceType::toResponse)
.map(::FilterableServiceType)
}
}
ApiDataSourceImpl.kt
class ApiDataSourceImpl(
private val apiService: ApiService,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
) : ApiDataSource {
override suspend fun getServices(token: String): List<ServiceModel> =
withContext(ioDispatcher) {
apiService.getServices(token = token)
}
override suspend fun getServiceTypes(token: String): List<ServiceType> =
withContext(ioDispatcher) {
apiService.getServiceTypes(token)
}
}
ServiceScreen.kt 的可组合 UI
@Composable
fun ServiceScreen(
serviceViewModel: ServiceViewModel,
onServiceClick: (serviceId: String) -> Unit,
) {
// Collect the UI states from the view model.
val servicesUiState by serviceViewModel.servicesUiState.collectAsStateWithLifecycle()
val serviceTypesUiState by serviceViewModel.serviceTypeUiState.collectAsStateWithLifecycle()
println("serviceTypesUiState: $serviceTypesUiState")
Column(
modifier = Modifier.fillMaxSize()
) {
when (val serviceTypesUiStateOb = serviceTypesUiState) {
is ServiceTypesUiState.Success -> FilterRow(
serviceTypesUiStateOb.serviceTypeResponses,
serviceViewModel
)
is ServiceTypesUiState.Loading -> {
CircularProgressBar()
}
is ServiceTypesUiState.Error -> {
Text(text = "Error: ${serviceTypesUiStateOb.exception.localizedMessage}")
}
}
when (val servicesUiStateOb = servicesUiState) {
is ServicesUiState.Loading -> CircularProgressBar()
is ServicesUiState.Success -> ServiceList(
servicesUiStateOb.services,
onServiceClick = onServiceClick
)
is ServicesUiState.Error -> servicesUiStateOb.exception.localizedMessage?.let {
Text(
text = it
)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FilterRow(
filterableServiceTypes: List<FilterableServiceType>,
serviceViewModel: ServiceViewModel,
) {
println("FilterRow: serviceTypesUiState : $filterableServiceTypes")
var isSelected by remember { mutableStateOf(false) }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
filterableServiceTypes.forEachIndexed { index, filterableServiceType ->
ElevatedFilterChip(
onClick = {
isSelected = !isSelected
},
label = {
Text(filterableServiceType.serviceType.type)
},
selected = isSelected,
leadingIcon = when {
isSelected -> {
{
Icon(
imageVector = Icons.Filled.Done,
contentDescription = "Filter services for ${filterableServiceType.serviceType.type}",
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
}
}
else -> {
null
}
}
)
}
}
}
sealed interface ServicesUiState {
data object Loading : ServicesUiState
data class Success(val services: List<Service>) : ServicesUiState
data class Error(val exception: Throwable) : ServicesUiState
}
sealed interface ServiceTypesUiState {
data object Loading : ServiceTypesUiState
data class Success(val serviceTypeResponses: List<FilterableServiceType>) : ServiceTypesUiState
data class Error(val exception: Throwable) : ServiceTypesUiState
}
服务类型业务逻辑的模型数据类:
data class ServiceType(
val id: String,
val type: String,
)
@Parcelize
data class ServiceTypeResponse(
val id: String,
val type: String,
) : Parcelable
class FilterableServiceType(
val serviceType: ServiceTypeResponse,
initialChecked: Boolean = false,
) {
var isSelected: Boolean by mutableStateOf(initialChecked)
}
fun ServiceType.toResponse() = ServiceTypeResponse(
id = id, type = type
)
当我启动应用程序并使用 NavHostController 从适当的路由导航到 ServiceScreen 时,我可以获取 /api/services 的数据,它正确地显示了 s 列表,但它不显示可组合项的 /api/services/types 的数据。我正在使用可组合项中的collectAsStateWithLifecycle收集状态。Service
FilterRow
ServiceScreen
答:
0赞
Jay
11/15/2023
#1
主要问题出在我在课堂上使用的流程构建器中。我正在调用 API,但从未从此流构建器发出任何内容。解决方案如下:ServiceRepositoryImpl
import com.example.domain.ServiceRepository
import com.example.domain.model.FilterableServiceType
import com.example.domain.model.Service
import com.example.repository.api.model.ServiceType
import com.example.repository.api.model.toResponse
import com.example.repository.api.model.toService
import com.example.repository.util.toBearerToken
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
class ServiceRepositoryImpl(private val apiDataSource: ApiDataSource) : ServiceRepository {
override fun getServices(tokenFlow: Flow<String>): Flow<List<Service>> {
return flow {
val token = tokenFlow.first().toString().toBearerToken()
val services = apiDataSource.getServices(token).map {
it.toService()
}
emit(services) // emitted here
}
}
override fun getServiceTypes(tokenFlow: Flow<String>): Flow<List<FilterableServiceType>> =
flow {
val token = tokenFlow.first().toString().toBearerToken()
emit( // missing emit call for this flow builder
apiDataSource.getServiceTypes(token)
.map(ServiceType::toResponse)
.map(::FilterableServiceType)
)
}
}
评论
ServiceRepositoryImpl#getServiceTypes(Flow<String>)
ApiDataSource#getServiceTypes(String)