无法在 ViewModel 中过滤数据 - Android Kotlin

Unable to Filter Data in ViewModel - Android Kotlin

提问人:Zobaer Hossain 提问时间:11/16/2023 最后编辑:Zobaer Hossain 更新时间:11/21/2023 访问量:82

问:

赏金将在 3 天后到期。这个问题的答案有资格获得 +50 声望赏金。佐巴尔·侯赛因(Zobaer Hossain)正在寻找来自信誉良好的来源的答案

从 API 获取数据时,我在 ViewModel 中过滤数据时遇到问题。我有一个 ViewModel (CharacterViewModel),它使用 getCharactersUseCase 检索数据。我想根据字符 ID 过滤此数据,并在可组合项 (CharacterDetailScreen) 中观察过滤后的结果。但是,我没有得到预期的结果。

CharacterView模型


  @HiltViewModel
  class CharacterViewModel @Inject constructor(
   private val getCharactersUseCase: GetCharactersUseCase
  ) : ViewModel() {
   private val _characterState: MutableStateFlow<PagingData<Character>> =
       MutableStateFlow(value = PagingData.empty())
   private val _characterDetailsState: MutableStateFlow<Character?> = MutableStateFlow(null)

   val charactersState: MutableStateFlow<PagingData<Character>> get() = _characterState
   val characterDetailsState: MutableStateFlow<Character?> get() = _characterDetailsState

   init {
       getCharacters()
   }

   private fun getCharacters() {
       viewModelScope.launch {
           getCharactersUseCase.execute(Unit)
               .distinctUntilChanged()
               .cachedIn(viewModelScope)
               .collect {
                   _characterState.value = it
               }
       }
   }
   fun getCharacterDetails(characterID: String) {
       Log.i("character"," character from viewmodel received id= ${characterID}")
       // Collect the latest charactersState and then filter the character
       viewModelScope.launch {
           charactersState.collect { characters ->
               characters
                   .filter { it.id == characterID }
                   .map { character ->
                       Log.i("character"," character from viewmodel id= ${character.id}")
                       _characterDetailsState.value = character
                   }
           }
       }
   }
}

CharacterDetailScreen(字符详细信息屏幕)


 @Composable
 fun CharacterDetailScreen(
    navController: NavController,
    viewModel: CharacterViewModel = hiltViewModel(),
     characterId: String
 )
 {
    // Collect character details state
    val characterDetailsState by viewModel.characterDetailsState.collectAsState()

    LaunchedEffect(characterDetailsState) {
        viewModel.getCharacterDetails(characterId)
    }
    // Use LaunchedEffect to trigger the getCharacterDetails call when the Composable is first       launched
    CharacterDetailsItem(characterDetailsState, characterId)
    }

CharacterDetailsItem

   
    @Composable
    fun CharacterDetailsItem(character_: Character?, characterId: String){
    Log.i("character"," character from details item ${character_?.name}")
    val character by remember { mutableStateOf(character_) }
    Box(modifier = Modifier.padding(top = 8.dp)) {
        Card(
            colors = CardDefaults.cardColors(
                containerColor = MaterialTheme.colorScheme.surfaceVariant,
            ),
            modifier = Modifier
                .fillMaxWidth()

        ) {
            Column {
                Text(
                    text = characterId,
                    modifier = Modifier
                        .padding(16.dp),
                    textAlign = TextAlign.Center,
                )
                character?.name?.let {
                    Text(
                        text = it,
                        modifier = Modifier
                            .padding(16.dp),
                        textAlign = TextAlign.Center,
                    )
                }
                character?.gender?.let {
                    Text(
                        text = it,
                        modifier = Modifier
                            .padding(16.dp),
                        textAlign = TextAlign.Center,
                    )
                }
            }
        }
    }
  }

洛卡特

2023-11-17 12:01:19.709  5486-5486  character               com.starwars.app                     I   character from details item null
2023-11-17 12:01:19.740  5486-5486  character               com.starwars.app                     I   character from viewmodel received id= 631a02dc-ecf2-4636-8e59-d180766e52cf
2023-11-17 12:01:19.760  5486-5486  character               com.starwars.app                     I   character from details item null
2023-11-17 12:01:20.472  5486-5486  character               com.starwars.app                     I   character from details item null
2023-11-17 12:01:20.524  5486-5486  character               com.starwars.app                     I   character from details item null

我提供了 ViewModel (CharacterViewModel)、Composable (CharacterDetailScreen) 和用于显示详细信息的 Composable (CharacterDetailsItem) 的代码片段。我将不胜感激,了解可能导致 ViewModel 中过滤逻辑出现问题的原因。

我试过什么:

第一:

    fun getCharacterDetails(characterID: String) {
    Log.i("character", " character from viewmodel received id= ${characterID}")
    // Collect the latest charactersState and then filter the character
    viewModelScope.launch {
     _characterDetailsState.value = Character("213","bday","e","g","h","hi","name","skin")
      
    }
}

这样,我就可以设置和获取价值,但不能从 charactersState 或过滤_characterState值中获取。

第二:

@Composable
fun CharacterDetailScreen(
navController: NavController,
viewModel: CharacterViewModel = hiltViewModel(),
characterId: String
) {
// Collect character details state
val characterPagingItems: LazyPagingItems<Character> = 
viewModel.charactersState.collectAsLazyPagingItems()

val selectedCharacter = characterPagingItems.itemSnapshotList
    .find { it?.id == characterId }

// Call CharacterDetailsItem when selectedCharacter is not null
selectedCharacter?.let { character ->
    CharacterDetailsItem(character, characterId) //this composable 
  //gets recomposed with null values
}
Text(text = "first") //this text stays

}

我的DI字符模块:

@Module
@InstallIn(ViewModelComponent::class)
object CharacterModule {
@Provides
@ViewModelScoped
fun providesCharacterRemoteDataSource(
    api: StarWarsApi
): CharacterRemoteDataSource {
    return CharacterRemoteDataSourceImpl(api)
}

@Provides
@ViewModelScoped
fun providesCharacterRepository(
    characterRemoteDataSource: CharacterRemoteDataSource,
    starWarsDatabase: StarWarsDatabase
): CharacterRepository {
    return CharacterRepositoryImpl(characterRemoteDataSource, 
starWarsDatabase)
}

@Provides
@ViewModelScoped
fun providesGetCharactersUseCase(
    characterRepository: CharacterRepository
): GetCharactersUseCase {
    return GetCharactersUseCase(characterRepository)
}
@Provides
@ViewModelScoped
fun providesGetCharacterDetailsUseCase(
    characterRepository: CharacterRepository
): GetCharacterDetailsUseCase {
    return GetCharacterDetailsUseCase(characterRepository)
}
@Provides
@ViewModelScoped
fun provideCharacterDetailsViewModel(
    getCharactersUseCase: GetCharactersUseCase
): CharacterViewModel {
    return CharacterViewModel(getCharactersUseCase)
}
}
Android Kotlin MVVM 视图模型

评论

0赞 Rob 11/17/2023
您在 Logcat 中获得哪些日志?
0赞 Zobaer Hossain 11/17/2023
@Rob我在 ViewModel 中设置的日志,过滤器函数永远不会被调用。我认为我在过滤乐趣后做错了什么。
0赞 Rob 11/17/2023
尝试直接在流上过滤 (.filter { it.id == characterID }),并在流上映射,并将值发送到收集块中 viewModel 的 stateFlow
0赞 Zobaer Hossain 11/19/2023
仍然不起作用。
0赞 Ankush 11/20/2023
你能检查你的用例getCharactersUseCase.execute(Unit)是否正常工作,并且你是否从中收到一个字符列表吗?

答:

1赞 Nazarii Moshenskiy 11/21/2023 #1

我认为你不应该收集里面的状态.如果要按搜索查询筛选状态列表。ViewModel

您可能需要考虑类似的解决方案:https://stackoverflow.com/a/72311648/7805359

评论

0赞 Zobaer Hossain 11/21/2023
感谢您提出另一种方法。但是,在实现它时,我遇到了将过滤后的值从 传递到可组合项的问题。一段时间后,传递的值似乎丢失了。我怀疑出现这个问题是因为 ViewModel 范围随着时间的推移变得无法访问。注意:我正在将单个依赖注入 (DI) 模块和 ViewModel 用于角色屏幕和角色详细信息屏幕。characterDetailsScreencharacterItemcharacterDetailsScreen
0赞 Ehsan Setayesh 11/25/2023 #2
Try this
fun getCharacterDetails(characterID: String) {
    Log.i("character"," character from viewmodel received id= 
     ${characterID}")
    // Collect the latest charactersState and then filter the character
    viewModelScope.launch {
        charactersState.collect { characters ->
            val filteredCharacters = characters.filter { it.id == 
            characterID }
            if (filteredCharacters.isNotEmpty()) {
                _characterDetailsState.value = filteredCharacters.first()
            } else {
                _characterDetailsState.value = null
            }
        }
    }
}

@Composable
fun CharacterDetailScreen(
    navController: NavController,
    viewModel: CharacterViewModel = hiltViewModel(),
    characterId: String
) {
    // Call getCharacterDetails directly
    viewModel.getCharacterDetails(characterId)

    // Collect character details state
    val characterDetailsState by 
    viewModel.characterDetailsState.collectAsState()

    // Use characterDetailsState in your composable
    CharacterDetailsItem(characterDetailsState, characterId)
}

评论

0赞 Zobaer Hossain 11/26/2023
谢谢你的回答。试过这个,但它不起作用。