PokeApi를 사용하며 이것저것 연습하고 있습니다!
현재 PokeApi는 검색 Query를 요청 할 수 없는 상태입니다.
Paging3를 이용하여 데이터를 불러오고, 포켓몬이름을 검색하면 검색된 포켓몬 리스트를 보여주고싶은데요..
검색을 할때마다 화면이 번쩍거리게 됩니다.
제 추측상 검색을 하게되면서 데이터를 재 생성하게 되면서 UI가 다시 그려지게되어 이런현상이 발생했다고는 생각합니다만.. 정말 저녁부터 몇시간째 이 문제하나를 풀지 못하여 이렇게 질문을 남깁니다!!
시도해본것)
- ViewModel에서 바로 데이터를 가져와서 UI에서 조건을 걸어보려고해도 SnapShot 으로 밖에 Paging 데이터를 가져올 수 없어서 이에따라 로드된 데이터만 보여지게 됩니다. .ㅠㅠ
- 현재 코드로 ViewModel에서 분기처리를 하여 필연적으로 검색을하면 데이터가 재생성됩니다...
질문할것)
1. 서버에 Query를 요청하지 않고도 화면 번쩍임을 없앨 수 있나요?
2. 가능하다면 어떤식으로 해결해야할지.. 모르겠습니다 도와주세요!!
Code)
ViewModel
@HiltViewModel
class HomeViewModel @Inject constructor(
private val getPokemonListUseCase: GetPokemonListUseCase
) : ViewModel() {
private val _searchQuery = MutableStateFlow("")
val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()
private val _appliedQuery = MutableStateFlow("")
val appliedQuery: StateFlow<String> = _appliedQuery.asStateFlow()
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
val pokemonPagingData: Flow<PagingData<PokemonList>> =
appliedQuery
.debounce(300L)
.distinctUntilChanged()
.flatMapLatest { query ->
getPokemonListUseCase().map { pagingData ->
if (query.isEmpty()) {
pagingData
} else {
pagingData.filter { it.name.contains(query, ignoreCase = true) }
}
}
}.cachedIn(viewModelScope)
fun updateSearchQuery(query: String) {
_searchQuery.update { query }
}
fun applySearchQuery() {
_appliedQuery.update { _searchQuery.value }
}
}
Home
const val GRID_COLUMN = 2
@Composable
fun HomeRoute(
navController: NavHostController,
homeViewModel: HomeViewModel = hiltViewModel()
) {
val pokemonList = homeViewModel.pokemonPagingData.collectAsLazyPagingItems()
val searchQuery by homeViewModel.searchQuery.collectAsStateWithLifecycle()
when (pokemonList.loadState.refresh) {
is LoadState.Loading -> {
LoadingScreen()
}
is LoadState.Error -> {
val error = (pokemonList.loadState.refresh as LoadState.Error).error
ErrorScreen(errorMessage = error.message ?: ErrorCode.UNKNOWN_ERROR.message) {
pokemonList.retry()
}
}
else -> {
HomeScreen(
pokemonList = pokemonList,
searchQuery = searchQuery,
onSearchQueryChange = { homeViewModel.updateSearchQuery(it) },
onSearch = { homeViewModel.applySearchQuery() },
onPokemonClick = { pokemonId ->
navController.navigate("detail/$pokemonId")
}
)
}
}
}
@Composable
fun HomeScreen(
pokemonList: LazyPagingItems<PokemonList>,
searchQuery: String,
onSearchQueryChange: (String) -> Unit,
onSearch: () -> Unit,
onPokemonClick: (Int) -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
) {
SearchCard(
query = searchQuery,
onQueryChange = onSearchQueryChange,
onSearch = onSearch
)
Box(modifier = Modifier.fillMaxSize()) {
LazyVerticalGrid(
columns = GridCells.Fixed(GRID_COLUMN),
) {
items(pokemonList.itemCount) { index ->
val pokemon = pokemonList[index]
if (pokemon != null) {
PokemonCard(
pokemon = pokemon,
onClick = { onPokemonClick(pokemon.id) }
)
}
}
}
pokemonList.apply {
when {
loadState.append is LoadState.Loading -> {
LoadingScreen()
}
loadState.append is LoadState.Error -> {
val error = (loadState.append as LoadState.Error).error
ErrorScreen(
errorMessage = error.message ?: ErrorCode.UNKNOWN_ERROR.message
) {
retry()
}
}
}
}
}
}
}
SearchCard
@Composable
fun SearchCard(
query: String,
onQueryChange: (String) -> Unit,
onSearch: () -> Unit
) {
val focusManager = LocalFocusManager.current
Row(
modifier = Modifier.padding(8.dp)
) {
OutlinedTextField(
value = query,
onValueChange = onQueryChange,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
placeholder = {
Text(
text = "검색어를 입력하세요",
color = Color.DarkGray
)
},
textStyle = TextStyle(color = Color.LightGray),
singleLine = true,
trailingIcon = {
IconButton(onClick = {
focusManager.clearFocus()
onSearch()
}) {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "검색"
)
}
},
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Search
),
keyboardActions = KeyboardActions(
onSearch = {
focusManager.clearFocus()
onSearch()
}
)
)
}
}
PokemonCard
@Composable
fun PokemonCard(
pokemon: PokemonList,
onClick: () -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.clickable { onClick() }
) {
AsyncImage(
model = pokemon.imageUrl,
contentDescription = "포켓몬 이미지",
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f),
contentScale = ContentScale.Fit
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = pokemon.name,
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
fontSize = 16.sp,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(8.dp)
)
}
}