Kotlin词典APP数据异常求助:Flow无数据显示问题排查
词典APP数据加载问题排查
问题现象
使用Kotlin开发词典APP,采用MVVM+Clean架构,通过Jsoup从网络抓取词汇信息,用Flow传递数据,但出现以下问题:
- 界面无词汇数据展示
- 控制台仅输出
kotlinx.coroutines.flow.SafeFlow@1493a74 MostUsedWordScreen中打印的state.mostWordUsedItems为空列表[]
相关代码
ExtractedData(数据实体类)
data class ExtractedData( var id :Int = 0, var word:String = "", var meaning :String = "" )
WordInfoRepositoryImpl(数据层实现)
class WordInfoRepositoryImpl @Inject constructor( private val api:DictionaryApi ) : WordInfoRepository { // 从网络获取词汇及释义 override fun getEventsList(): Flow<Resource<MutableList<ExtractedData>>> = flow { emit(Resource.Loading()) val listData = mutableListOf<ExtractedData>() try { val url = "https://ielts.com.au/australia/prepare/article-100-new-english-words-and-phrases-updated-2020" val doc = withContext(Dispatchers.IO){ Jsoup.connect(url).get() // 提示"Inappropriate blocking method call",但已放在IO协程中 } val table = doc.select("table") val rows = table.select("tr") val eventsSize = rows.size for (i in 1 until eventsSize) { val row = rows[i] val cols = row.select("td") val word = cols[0].text() val meaning = cols[1].text() listData.add(ExtractedData(i,word,meaning)) } } catch (e: IOException) { emit(Resource.Error("IO Exception")) } catch (e : HttpException) { emit(Resource.Error("HTTP EXCEPTION")) } emit(Resource.Success(listData)) } }
WordInfoRepository(领域层接口)
interface WordInfoRepository { fun getEventsList(): Flow<Resource<MutableList<ExtractedData>>> }
GetWordsAndMeaningsOnTheInternetUseCase(领域层用例)
class GetWordsAndMeaningsOnTheInternetUseCase@Inject constructor( private val repository: WordInfoRepository ){ operator fun invoke() : Flow<Resource<MutableList<ExtractedData>>> { return repository.getEventsList() } }
MostUsedWordScreenViewModel(ViewModel层)
@HiltViewModel class MostUsedWordScreenViewModel @Inject constructor( private val getWordsAndMeaningsOnTheInternetUseCase: GetWordsAndMeaningsOnTheInternetUseCase ) : ViewModel() { private var searchJob: Job? = null private val _state = mutableStateOf(MostUsedWordState()) val state: State<MostUsedWordState> = _state init { fetchData() } private fun fetchData() { searchJob?.cancel() searchJob = viewModelScope.launch(IO) { getWordsAndMeaningsOnTheInternetUseCase().onEach { result -> when (result) { is Resource.Success -> { _state.value = state.value.copy( mostWordUsedItems = result.data ?: mutableListOf(), isLoading = false ) } is Resource.Error -> { _state.value = state.value.copy( mostWordUsedItems = result.data ?: mutableListOf(), isLoading = false ) } is Resource.Loading -> { _state.value = state.value.copy( mostWordUsedItems = result.data ?: mutableListOf(), isLoading = true ) } } } } } }
MostUsedWordScreen(UI层)
@Composable fun MostUsedWordScreen(viewModel: MostUsedWordScreenViewModel = hiltViewModel()) { val state = viewModel.state.value println("state --- >>> "+state.mostWordUsedItems) LazyColumn(modifier = Modifier.fillMaxSize()) { items(state.mostWordUsedItems.size) { i -> val wordInfo = state.mostWordUsedItems[i] if(i > 0) { Spacer(modifier = Modifier.height(8.dp)) } MostUsedWordItem(word = wordInfo) if(i < state.mostWordUsedItems.size - 1) { Divider() } } } } @Composable fun MostUsedWordItem(word : ExtractedData ) { Column(modifier = Modifier .padding(5.dp) .fillMaxWidth()) { Text(text = word.word, modifier = Modifier.padding(3.dp), textAlign = TextAlign.Center, fontSize = 18.sp) } }
问题定位与解决方案
核心问题:Flow未被收集
Flow是冷流,只有被收集时才会执行流中的逻辑。原ViewModel代码中,getWordsAndMeaningsOnTheInternetUseCase().onEach { ... }仅定义了流的转换操作,但没有启动收集,导致整个网络请求和数据解析逻辑完全没执行,自然没有数据更新到UI状态。
修复代码(ViewModel的fetchData方法)
将onEach配合launchIn启动收集,或者直接使用collect:
private fun fetchData() { searchJob?.cancel() searchJob = viewModelScope.launch { getWordsAndMeaningsOnTheInternetUseCase() .onEach { result -> when (result) { is Resource.Success -> { _state.value = state.value.copy( mostWordUsedItems = result.data ?: mutableListOf(), isLoading = false ) } is Resource.Error -> { _state.value = state.value.copy( mostWordUsedItems = result.data ?: mutableListOf(), isLoading = false ) } is Resource.Loading -> { _state.value = state.value.copy( // Loading状态保留原有数据,避免列表清空 isLoading = true ) } } } // 启动Flow收集 .launchIn(viewModelScope) } }
次要优化点
- Loading状态保留原有数据:原代码在Loading时会把
mostWordUsedItems设为空列表,导致界面清空,建议仅更新isLoading状态,保留现有数据。 - Jsoup选择器准确性:检查目标页面的HTML结构,确保
doc.select("table")能正确定位到包含词汇的表格,必要时使用更精确的选择器(比如doc.select("table[class='some-class']"))。 - Jsoup阻塞警告:虽然不影响功能,但可以通过
@Suppress("BlockingMethodInNonBlockingContext")注解消除警告,或者将整个Jsoup操作块放在withContext(Dispatchers.IO)内(已做)。
内容的提问来源于stack exchange,提问作者NewPartizal
相关产品推荐
相关产品推荐

