我有这个应用程序,它显示了一个";硬币";给用户。这个列表是从JSON API解析的,我使用Jetpack Compose作为UI。我实施了
这里是Jetpack可组合列表的代码;硬币";
@Composable
fun CoinListScreen(
navController: NavController,
viewModel: CoinListViewModel = hiltViewModel(),
) {
val state = viewModel.state.value
Surface {
Box(modifier = Modifier.fillMaxSize()) {
Column {
androidx.compose.foundation.Image(painter = painterResource(id = R.drawable.ic_baseline_currency_bitcoin_24),
contentDescription = "BTC",
modifier = Modifier
.fillMaxWidth()
.align(CenterHorizontally)
.size(50.dp, 50.dp)
)
SearchBar(
hint = "Search..",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
){
viewModel.searchCoinsList(it) **//here I'm calling my search function from the view model, inside my search bar**
}
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(state.coins) { coin ->
Spacer(modifier = Modifier.height(5.dp))
CoinListItem(
coin = coin,
onItemClick = {
navController.navigate(Screen.CoinDetailScreen.route + "/${coin.id}")
}
)
Divider()
}
}
}
if (state.error.isNotBlank()) {
Text(
text = state.error,
color = MaterialTheme.colors.error,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
.align(Alignment.Center)
)
}
if (state.isLoading) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
}
}
}
**//and this Is my composable search bar**
@Composable
fun SearchBar(
modifier: Modifier = Modifier,
hint: String = "",
onSearch: (String) -> Unit = {}
) {
var text by remember {
mutableStateOf("")
}
var isHint by remember {
mutableStateOf(hint != "")
}
Box(modifier = modifier){
BasicTextField(
value = text,
onValueChange = {
text = it
onSearch(it)
},
maxLines = 1,
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.shadow(5.dp, CircleShape)
.background(Color.White, CircleShape)
.padding(horizontal = 20.dp, vertical = 12.dp)
.onFocusChanged {
isHint = it.isFocused != true
}
)
if(isHint){
Text(
text = hint,
color = Color.LightGray,
modifier = Modifier.padding(horizontal = 20.dp, vertical = 12.dp)
)
}
}
}
这是我的视图模型,这是我实现搜索功能的地方,这是我们迷失的地方,我搜索的变量是Coin域名列表中的名称、等级和符号
@HiltViewModel //injecting the use case
class CoinListViewModel @Inject constructor (
private val getCoinsUseCase: GetCoinsUseCase,
) : ViewModel() {
//vmstate Live Template, only the view model touches it
private val _state =
mutableStateOf(CoinListState())
val state: State<CoinListState> = _state
**//for search purposes , this is where I'm lost**
private var coinsList = mutableStateOf<List<Coin>>(listOf())
private var cachedCoinsList = listOf<Coin>()
private var isSearchStarting = true
private var isSearching = mutableStateOf(false)
init {
getCoins()
}
**//for search purposes , this is where I'm lost**
fun searchCoinsList(query: String){
val listToSearch = if(isSearchStarting){
coinsList.value
} else {
cachedCoinsList
}
viewModelScope.launch(Dispatchers.Default) {
if(query.isEmpty()){
coinsList.value = cachedCoinsList
isSearching.value = false
isSearchStarting = true
return@launch
}
val results = listToSearch.filter {
//val iterate: Int = coins.size
it.name.contains(query.trim(), ignoreCase = true) ||
(it.rank.toString() == query.trim()) ||
it.symbol.contains(query.trim(), ignoreCase = true)
}
if(isSearchStarting){
cachedCoinsList = coinsList.value
isSearchStarting = false
}
coinsList.value = results
isSearching.value = true
}
}
//function that calls our GetCoinsUseCase and puts the data inside the state object
//to display that in the UI
private fun getCoins() {
//overwrote the invoke function earlier for the use case which allows us to call the use case as a function
getCoinsUseCase().onEach { result ->
when (result) {
is Resource.SUCCESS -> {
_state.value =
CoinListState(coins = result.data ?: arrayListOf())
}
is Resource.ERROR -> {
_state.value =
CoinListState(
error = result.message ?: "An unexpected error occurred"
)
}
is Resource.LOADING -> {
_state.value = CoinListState(isLoading = true)
}
}
}.launchIn(viewModelScope)
}
}
视图模型中使用的CoinsListState数据类
data class CoinListState(
val isLoading: Boolean = false,
val coins: ArrayList<Coin> = arrayListOf(),
val error: String = ""
)
这是我的";GetCoinsUseCase;获取硬币
class GetCoinsUseCase @Inject constructor(
private val repository: CoinRepository
) {
// overwriting the operator fun invoke allows us to call the use case
//GetCoinsUseCase as if it was a function, and we return a flow because
// we want to emit states LOADING -> for progress bar, SUCCESS -> attach list of coins,
// and ERROR
operator fun invoke(): kotlinx.coroutines.flow.Flow<Resource<ArrayList<Coin>>> = flow {
try {
emit(Resource.LOADING<ArrayList<Coin>>())
//we mapped it to toCoin because we returning a list of coin, not coinDTO
val coins = repository.getCoins().map { it.toCoin() }
emit(Resource.SUCCESS<ArrayList<Coin>>(coins as ArrayList<Coin>))
}catch (e: HttpException){
emit(Resource.ERROR<ArrayList<Coin>>(e.localizedMessage ?: "An unexpected error occurred"))
}catch (e: IOException){
emit(Resource.ERROR<ArrayList<Coin>>("Couldn't reach server. Check connection"))
}
}
}
只是在另一个地方实现的硬币存储库
interface CoinRepository {
//repository definitions
suspend fun getCoins() : ArrayList<CoinDTO>
suspend fun getCoinById(coinId: String) : CoinDetailDTO
}
这是我的域-域-只包含所需的数据数据类Coin(var id:字符串,var isActive:布尔值,var名称:字符串,var秩:Int,var符号:字符串)这就是我绘制的方式
data class CoinDTO(
val id: String,
@SerializedName("is_active")
val isActive: Boolean,
@SerializedName("is_new")
val isNew: Boolean,
val name: String,
val rank: Int,
val symbol: String,
val type: String
)
fun CoinDTO.toCoin(): Coin {
return Coin(
id = id,
isActive = isActive,
name = name,
rank = rank,
symbol = symbol,
// logo = CoinDetailLogo(logo = String()).logo
)
}
硬币列表项目如果需要参考,这是在列表中向用户显示的内容
@Composable
fun CoinListItem (
coin: Coin,
onItemClick: (Coin) -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { onItemClick(coin) }
.padding(20.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "${coin.rank}. ${coin.name} (${coin.symbol})",
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Ellipsis
)
Text(
text = if(coin.isActive) "active" else "inactive",
color = if(coin.isActive) Color.Green else Color.Red,
fontStyle = FontStyle.Italic,
textAlign = TextAlign.End,
style = MaterialTheme.typography.body2,
modifier = Modifier.align(CenterVertically)
)
}
}
以及";资源";状态通用
//UIStates
sealed class Resource<T>(val data: T? = null, val message: String? = null) {
class SUCCESS<T>(data: T) : Resource<T>(data)
class ERROR<T>(message: String, data: T? = null) : Resource<T>(data, message)
class LOADING<T>(data: T? = null) : Resource<T>(data)
}
同样,鉴于这些信息,当在搜索栏内的CoinListScreen中调用函数searchCoinList时,我如何在视图模型中获得函数searchCoin List,以正确查看搜索到的数据(名称、等级或符号(。非常感谢
您似乎想要实现一个基本的即时搜索功能。使用Kotlin的StateFlow
及其运算符很容易实现。考虑以下描述实现:
// CoinListViewModel
private val queryFlow = MutableStateFlow("")
private val coinsList = mutableStateOf<List<Coin>>(listOf())
init {
queryFlow
.debounce(300) // filters out values that are followed by the newer values within the given timeout. The latest value is always emitted.
.filterNot { query -> userInput.isEmpty() } // filter the unwanted string like an empty string in this case to avoid the unnecessary network call.
.distinctUntilChanged() // to avoid duplicate network calls
.flowOn(Dispatchers.IO) // Changes the context where this flow is executed to Dispatchers.IO
.flatMapLatest { query -> // to avoid the network call results which are not needed more for displaying to the user
getCoinsUseCase(query).catch { emitAll(flowOf(emptyList())}
}
.onEach { coins: List<Coin> -> // go through each list of Coins
coinsList.value = coins
}
.launchIn(viewModelScope)
}
fun searchCoinsList(query: String) {
queryFlow.value = query
}