티스토리 뷰

안드로이드/코드

Mj App : 게임 - 포켓몬

알렌보이스 2023. 6. 27. 20:11
728x90

1. 데이터베이스와 API


1) 테이블


[pokemon]

포켓몬의 정보가 담긴 테이블입니다.

[characteristic]

포켓몬의 특성 정보가 담긴 테이블입니다.

[evolution]

포켓몬의 진화 정보가 담긴 테이블입니다.

[evolution_type]

포켓몬의 진화 타입이 담긴 테이블입니다.

 

2) 포켓몬 리스트 조회 api


@app.get("/pokemonList")
async def read_pokemon_list(name: str = "", skip: int = 0, limit: int = 100):
    session.commit()

    list = session.query(PokemonTable.index, PokemonTable.number, PokemonTable.name, PokemonTable.spotlight,
                         PokemonTable.shinySpotlight, PokemonTable.isCatch) \
        .filter(PokemonTable.name.like(f"%{name}%")).offset(skip).limit(limit).all()
    total_size = session.query(PokemonTable).filter(PokemonTable.name.like(f"%{name}%")).count()
    return {
        "list": list,
        "totalSize": total_size
    }

조회할 때 offset에 값을 넣으면 시작 인덱스를 설정할 수 있습니다.

limit을 설정하면 offset부터 지정한 개수만큼 조회가 가능합니다.

count를 이용하면 조건에 맞는 데이터의 개수를 받아올 수 있습니다.

위에 조건에 맞는 포켓몬 리스트와, 전체 개수를 조회합니다.

 

3) 포켓몬 상세 조회 api


@app.get("/pokemon/detail/{number}")
async def read_pokemon_detail(number: str):
    pokemon = session.query(PokemonTable).filter(PokemonTable.number == number).first()
    beforeInfo = await read_pokemon_image(pokemon.index - 1)
    nextInfo = await read_pokemon_image(pokemon.index + 1)
    evolutionInfo = await read_pokemon_evolution(number)

    return {
        "pokemonInfo": pokemon,
        "beforeInfo": beforeInfo,
        "nextInfo": nextInfo,
        "evolutionInfo": evolutionInfo
    }

@app.get("/pokemon/image/{index}")
async def read_pokemon_image(index: int):
    return session.query(PokemonTable.number, PokemonTable.image, PokemonTable.shinyImage).filter(
        PokemonTable.index == index).first()

@app.get("/pokemon/select/evolution")
async def read_pokemon_evolution(number: str):
    pokemon1 = aliased(PokemonTable)
    pokemon2 = aliased(PokemonTable)
    evolution = session.query(pokemon1.spotlight.label('beforeDot'), pokemon1.shinySpotlight.label('beforeShinyDot'), pokemon2.spotlight.label('afterDot'), pokemon2.shinySpotlight.label('afterShinyDot'), EvolutionTypeTable.image.label('evolutionImage'), EvolutionTable.evolutionCondition)\
        .filter(EvolutionTable.numbers.like(f"%{number}%"), EvolutionTable.beforeNum == pokemon1.number, EvolutionTable.afterNum == pokemon2.number, EvolutionTypeTable.name == EvolutionTable.evolutionType).all()

    return evolution

선택한 포켓몬 정보와 이전/다음 포켓몬의 이미지를 조회합니다.

 

4) 포켓몬 잡은 상태 업데이트 api


@app.post("/update/pokemon/catch")
async def update_pokemon_is_catch(item: UpdateIsCatch):
    pokemon = session.query(PokemonTable).filter(PokemonTable.number == item.number).first()
    pokemon.isCatch = item.isCatch
    session.commit()
    return f"{item.number} 업데이트 완료"

포켓몬 잡은 상태를 업데이트합니다.

 

2. UI


1) 포켓몬 도감 화면


화면 진입 시 전체 도감에 대한 리스트를 보여줍니다.

리스트에서 포켓몬을 선택 시 2번 다이얼로그가 표시됩니다.

다이얼로그에서는 포켓몬 정보를 보여줍니다.

또한 잡았는지 여부와 카운터 등록 등의 기능을 수행할 수 있습니다.

LazyVerticalGrid(
    columns = GridCells.Fixed(4),
    state = state,
    horizontalArrangement = Arrangement.spacedBy(10.dp),
    verticalArrangement = Arrangement.spacedBy(10.dp),
    contentPadding = PaddingValues(top = 15.dp, bottom = 50.dp),
    modifier = Modifier
        .fillMaxWidth()
        .weight(1f)
) {
    items(viewModel.list.size) {
        PokemonItem(
            info = viewModel.list[it],
            isShiny = viewModel.isShiny.value,
            onClick = { number ->
                viewModel.updateSelectNumber(number)
                isDetailDialogShow.value = true
            }
        )
    }
}

포켓몬 리스트는 LazyVerticalGrid를 이용하여 표현을 하였습니다.

GridCells.Fixed를 이용하여 4개 고정으로 표현을 하였는데 이렇게 하면 가로 화면이나 태블릿 처럼 가로가 길게 되면 이미지 크기가 엄청 커진다는 단점은 있습니다.

FlowLayout을 사용하여 이미지 크기를 고정하고 화면의 크기에 따라 아이템 개수를 조절할 수 있습니다.

하지만 이 경우에는 여백이 크게 발생할 수 있다는 점이 단점입니다.

이 앱은 출시 용이 아닌 저를 위한 앱이기 때문에 현재 사용 중인 S20+에만 맞추면 되는 것이라서 Grid를 선택하였습니다.

우측 상단의 ✦아이콘을 클릭하면 리스트의 포켓몬 이미지를 다른 색상 이미지로 변경할 수 있습니다.

검색 아이콘을 클릭하면 검색 다이얼로그가 표시되며 검색을 통해 원하는 포켓몬을 검색할 수 있습니다.

포켓몬 도감 조회는 페이징 처리가 되어있는 Api를 통해 데이터를 가져옵니다.

처음에는 페이징 라이브러리를 이용해서 구현을 하려고 하였고 데이터를 가져오는 것까지는 성공하였습니다.

하지만 리스트의 상태 변화가 일어나거나 다이얼로그를 띄우는 등의 행동을 하였을 때

리스트가 비워지고 새로 로드하는 현상이 있었습니다.

이를 해결하기 위해 여러 가지 시도를 해보았는데 해결을 못 하였습니다.

기존 xml을 이용할 때에도 페이징 라이브러리를 이용을 안 해서 이 부분은 추후에 더 알아봐야겠습니다.

그래서 다른 방식으로 구현할 필요가 있었습니다.

val state = rememberLazyGridState()

LaunchedEffect(state.firstVisibleItemIndex) {
    if (viewModel.isMoreDate.value) {
        viewModel.fetchMoreData(state.firstVisibleItemIndex)
    }
}

State의 firstVisibleItemIndex를 이용하는 방법입니다.

LaunchedEffect(state.firstVisibleItemIndex)를 하면 스크롤을 통해 최상위 아이템이 변경되었을 때 동작을 수행할 수 있습니다.

private fun fetchPokemonDex() = viewModelScope.launch {
    repository.fetchPokemonList(
        name = _search.value,
        skip = page,
        limit = limit
    ) { isMoreDate, list ->
        _isMoreDate.value = isMoreDate
        _list.addAll(list)
    }
}

fun fetchMoreData(index: Int) {
      if (index >= 20 + (page * limit)) {
          page += 1
          fetchPokemonDex()
      }
}

ViewModel에서는 특정 지점에 도달했다고 판단되었을 때 새로운 리스트를 조회합니다.

2. 포켓몬 카운터 화면


잡고 싶은 포켓몬을 등록하여 원하는 포켓몬을 잡는 데 얼마나 걸렸는지 카운팅을 하는 화면입니다.

기본적으로 1과 10단위로 카운트를 할 수 있으나 10단위는 설정에서 변경할 수 있습니다.

이 화면은 큰 기능이나 디자인이 없기 때문에 코드는 생략합니다.

728x90

'안드로이드 > 코드' 카테고리의 다른 글

코드 분석하기  (0) 2023.06.28
Mj App : 달력  (0) 2023.06.27
Mj App : 게임 - 엘소드  (0) 2023.06.27
Mj App : 공통  (0) 2023.06.27
Compose 내 관리앱 만들기 : 3. 교통 - 지하철  (0) 2023.03.19
댓글