오늘 공부한 내용 정리(2024년 8월 1일)
알고리즘 문제풀이
크냑과 3D 프린터(Silver 5, 30923번, 마라톤)
https://rkdrkd-history.tistory.com/170
동가수열 구하기(Silver 4, 25184번, 마라톤)
https://rkdrkd-history.tistory.com/171
앱 개발 심화 개인 과제
검색 페이지 구현
API Response data class 정의
카카오 API 문서에서 확인할 수 있는 Response를 이용해 API 호출 결과를 담을 데이터 클래스를 만들어 준다.
Response 구조에 맞게 data class를 구성해 주어야 한다.
data class ResultResponse(
val documents: List<ResultDocument>
)
data class ResultDocument(
val title: String,
val imageUrl: String,
val datetime: String
)
위 2개의 데이터 클래스에는 image, video 검색 결과 데이터가 들어갈 것이다.
image, video 검색 response data class로 위 데이터 클래스와 구조는 동일하다(response key만 다르다).
카카오 검색 API 구현
카카오 image 검색, video 검색 함수를 만들어 준다.
interface KakaoApi {
@GET("v2/search/image")
suspend fun getSearchImages(
@Header("Authorization") authorization: String = "KakaoAK ${BuildConfig.KAKAO_API_KEY}",
@Query("query") query: String,
@Query("sort") sort: String = "recency"
): SearchImageResponse
@GET("v2/search/vclip")
suspend fun getSearchVideos(
@Header("Authorization") authorization: String = "KakaoAK ${BuildConfig.KAKAO_API_KEY}",
@Query("query") query: String,
@Query("sort") sort: String = "recency"
): SearchVideoResponse
}
suspend function으로 만들어 주면 비동기적으로 앱이 동작하면서 API가 호출될 수 있도록 만들 수 있다.
데이터 매핑
API 호출로 인한 데이터를 불러와서 image list, video list를 모두 더해 줄 것이다.
그 작업은 repository에서 해 주었다.
class KakaoSearchRepository {
companion object {
val kakoApi = KakaoApiClient.kakaoApi
}
suspend fun getSearchImages(query: String): SearchImageResponse {
return kakoApi.getSearchImages(query = query)
}
suspend fun getSearchVideos(query: String): SearchVideoResponse {
return kakoApi.getSearchVideos(query = query)
}
fun combinationResult(images: SearchImageResponse, videos: SearchVideoResponse): List<ResultDocument> {
return ResponseMapper.searchImageToResult(images).documents + ResponseMapper.searchVideoToResult(videos).documents
}
}
combinationResult에서 기존의 SearchImageResponse, SearchVideoResponse를 ResultResponse로 변경해 준다.
매핑 함수는 내 깃허브 링크를 하단에 남겨놓을 테니 참고하길 바란다.
ViewModel에서 비즈니스 로직 처리
마지막으로 ViewModel에 repository의 함수를 구현해 준다.
ViewModel이 구현되면 View에서는 Repository 코드를 쓰는 게 아닌, ViewModel의 코드를 쓸 것이다.
class SearchViewModel(private val repository: KakaoSearchRepository = KakaoSearchRepository()): ViewModel() {
private val _searchResult = MutableStateFlow<List<ResultDocument>>(emptyList())
val searchResult: StateFlow<List<ResultDocument>> = _searchResult.asStateFlow()
private val _isLoading = MutableStateFlow<Boolean?>(null)
val isLoading: StateFlow<Boolean?> = _isLoading.asStateFlow()
fun getSearchList(query: String) {
viewModelScope.launch {
_isLoading.value = false
val searchImages = repository.getSearchImages(query)
val searchVideos = repository.getSearchVideos(query)
val result = repository.combinationResult(searchImages, searchVideos)
_searchResult.value = result.sortedByDescending { it.datetime }
if (result.isNotEmpty()) _isLoading.value = true
}
}
}
getSearchList로 같은 검색어의 image, video API를 호출하고, 그 결과를 repository의 혼합 함수로 새로운 리스트를 만들어 준다.
그 리스트에 sorting 작업을 해서 날짜 기준으로 상단으로 보이게 해 주었다.
UI에서 ViewModel 사용
UI에서 ViewModel에 정의한 StateFlow를 compose에서 불러오는 방법은 쉽다. collectAsState()로 불러올 수 있다.
val context = LocalContext.current
val searchList = searchViewModel.searchResult.collectAsState()
val isLoading = searchViewModel.isLoading.collectAsState()
TextField를 직접 만들어 커스터마이징 해주었다.
BasicTextField2(
modifier = Modifier.fillMaxWidth(),
state = searchKeywordState,
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(
onSearch = {
val searchQuery = searchKeywordState.text.toString()
if (searchQuery.isNotEmpty()) {
searchViewModel.getSearchList(searchQuery)
} else Toast.makeText(context, "검색어가 입력되지 않았습니다.", Toast.LENGTH_SHORT).show()
keyBoardController?.hide()
focusManager.clearFocus()
}
),
decorator = {
}
)
keyBoardOptions를 이용해 검색 전용 키보드를 열고, keyBoardActions에서 입력된 텍스트를 불러와 viewModel의 함수를 수행한다. 또한 검색 전용 키보드라 키보드 하단에 검색 버튼이 있는데 그것을 클릭했을 때의 이벤트를 수행하는 게 KeyboardActions의 onSearch이다.
decorator에서는 TextField를 커스터마이징 하는 곳이다.
decorator 블록 안에서 마음대로 커스터마이징 하면 된다.
decorator의 innerTextField는 입력이 되게 해주는 것이기 때문에 무조건 넣어 주어야 한다.
이제 검색이 되고 나면 화면에 보일 리스트를 만들어 주어야 한다.
LazyVerticalStaggeredGrid를 이용해 Grid리스트에 고정된 height를 가지지 않는 리스트를 만들어 줄 것이다(구현 영상을 보면 이해가 될 것이다).
if (isLoading.value == true) {
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2),
contentPadding = PaddingValues(8.dp),
verticalItemSpacing = 8.dp,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(searchList.value) {
SearchResultItem(item = it)
}
}
} else if (isLoading.value == false) {
LoadingIndicator(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
)
}
위에 만들어 놓은 isLoading으로 검색될 때마다 api가 불러와지는 시간을 loading indicator로 때워 준다.
다 불러와지면 indicator가 사라지고 LazyList가 뜬다.
구현 영상
풀 코드는 깃허브에서 확인해 보시길.
https://github.com/ImGaram/nb_camp_project/tree/master/image_locker
오늘 공부 내용 정리 및 회고
compose와 mvvm, repository 패턴까지 써보았다.
파일의 개수가 많아지니까 구현 시간도 그만큼 늘어난다. 이는 어쩔 수 없다.
하지만 디자인 패턴을 적용하면서 유지 보수성을 높이고 의존성을 줄일 수 있다.
지금 과제 같은 소규모 프로젝트에서는 단지 귀찮은 작업이 될 수도 있지만, 대형 프로젝트에서는 구현해 놓은 디자인 패턴이 빛을 발하게 될 것이다.
'♞ | 공부일지 > ♝ | TIL' 카테고리의 다른 글
[Kotlin] 공부일지(2024-08-05) (0) | 2024.08.05 |
---|---|
[Android, 내일배움캠프] 공부일지(2024-08-02) (0) | 2024.08.02 |
[Android, 내일배움캠프] 공부일지(2024-07-31) (0) | 2024.07.31 |
[Android, 내일배움캠프] 공부일지(2024-07-30) (0) | 2024.07.30 |
[Android, 내일배움캠프] 공부일지(2024-07-29) (0) | 2024.07.29 |