오늘은 좀 여유있게 진행할 수 있었다.
구현할 게 얼마 남지 않기도 했고 시간도 아직 여유가 좀 있었기 때문이다.
그래서 거의 마무리라고 생각되는 정도까지 도착해서 이른 오후에 정리해놓으려고 한다.
이제 여기서 더 한다면 어떤 기능들을 넣어야할지 생각을 내가 스스로 하고 넣어야하는 것이기 때문에 쉽지 않다...
하라는걸 하는데에는 크게 어려움을 느끼지 않는 경우가 많은데 처음부터 알아서 하라고 하면 좀 어려움을 느끼는 경우가 많다.
1. 뷰모델 팩토리 사용
이걸 사용하게 된 계기는 SharedPreferences다.
저번 글에서도 말했지만 나는 이걸 뷰모델 안에서 호출하고 싶었다.
하지만 SharedPreferences를 만들 때는 Context가 필요했고 뷰모델에는 존재하지 않았다.
그래서 이리저리 방법을 찾아보다가 나온건 AndroidViewModel을 상속받아 뷰모델을 구성하는 방법이었다.
챗GPT한테 물어본 거였는데 이전에 수업을 들으면서 안 쓰는 걸 매우 권장받았었기 때문에 이걸 들고 튜터님께 질문하러 갔다.
당연히 돌아온 답은 쓰지 말라는 것이었고 제시해주신 방향은 뷰모델 팩토리에 있었다.
뷰모델 팩토리로 Context를 넘기고 뷰모델 팩토리에서 뷰모델로 SharedPreferences를 넘기면 사용이 가능했다.
class MainViewModelFactory(private val context: Context): ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(context.getSharedPreferences("pref", Context.MODE_PRIVATE), context.getSharedPreferences("favorite", Context.MODE_PRIVATE)) as T
}
throw IllegalArgumentException("Unknown viewModel Class")
}
}
fun loadWordSharedPreferences() {
updateLastWord(prefSharedPreferences.getString("searchWord", "") ?: "")
}
fun loadDataSharedPreferences() {
val favoriteData =
favoriteSharedPreferences.getString("favorite_data", "")
if (favoriteData != null) restoreFavoriteData(favoriteData)
}
fun saveDataSharedPreferences() {
val gsonBuilder = GsonBuilder()
gsonBuilder.registerTypeAdapter(Document::class.java, DocumentTypeAdapter())
val editor = favoriteSharedPreferences.edit()
val favoriteData = gsonBuilder.create().toJson(storageUiState.value?.selectedResult)
editor.putString("favorite_data", favoriteData)
editor.apply()
}
fun saveWordSharedPreferences() {
val edit = prefSharedPreferences.edit()
edit.putString("searchWord", lastWord.value)
edit.apply()
}
메인 액티비티와 검색 결과 프래그먼트에 있던 관련함수들을 다 뷰모델 안으로 넣을 수 있게 됐고 조금더 MVVM 패턴에 맞춰서 짠 거 같았다.
2. 뷰모델 중복코드 줄이기
이건 봐도봐도 너무 길었다.
물론 sealed class 안에 있는 data class를 구분하기 위해서는 필요한 조건문이고 완전히 동일한 코드를 쓰는데도 다른 방법이 떠오르지 않았기 때문에 진행을 했지만 줄일 필요를 많이 느꼈다.
코드 알고리즘은 바꿀 수 없어서 시도한 것은 when 조건문을 최대한 안쪽으로 집어넣는 것과 중복 호출되는 변수와 함수 조합을 하나의 변수로 만들어 사용한 것이다.
fun manageSelectedDocument(selectedResult: Document) {
val list = storageUiState.value?.selectedResult // <--
if (list?.find { it == selectedResult } != null) {
...
} else {
_storageUiState.value = storageUiState.value?.copy(
selectedResult = list.orEmpty().toMutableList()
.apply {
add(
when (selectedResult) { // <--
is Document.ImageDocument -> {
selectedResult.copy(
isSelected = !selectedResult.isSelected
)
}
is Document.VideoDocument -> {
selectedResult.copy(
isSelected = !selectedResult.isSelected
)
}
}
)
...
}
)
}
}
물론 가장 좋은 방법은 when을 안 쓰고 들어온 입력 데이터의 타입을 알아차려서 그에 맞춰 캐스팅 해주는 방법이겠지만 아직 찾는 중이고 알지 못하기 때문에 이 상태가 가장 최선이라고 생각한다.
뷰모델에서 이런 방식을 채택해 다 수정했다.
3. 무한 스크롤 기능 구현
어제는 어떻게 접근해야할지 감이 잘 오지 않아서 손도 못댄 기능이었지만 오늘은 왠일인지 방법이 번뜩번뜩 떠올라서 구현하는데 성공했다.
먼저는 리사이클러뷰 스크롤을 감지하며 최하단에 도착했을 때 다음 페이지의 데이터를 가져오도록 뷰모델의 함수를 불렀다.
if (recyclerView.canScrollVertically(1).not()) {
viewModel.communicationNetwork(etSearch.text.toString(), page++)
}
여기서 좀 아직 수정해야겠다고 생각이 드는 부분은 page 부분이다.
검색 버튼을 누를 때는 무조건 1페이지 데이터를 불러오고 이후에 스크롤을 아래로 해서 최하단에 닿았을 때는 현재 페이지 이후의 페이지 데이터를 불러오도록 하는 방법이 뷰모델로 넘기는 걸로는 떠오르지 않더라...
그래서 지금은 프래그먼트에서 조절하도록 하는 방식을 채택하고 있지만 수정예정이다.
수정하면 다시 내용을 추가하겠다.
그리고 뷰모델로 가서 호출된 함수를 보면 이전에 프래그먼트에 있던 걸 그대로 옮겼기 때문에 거의 달라진 거는 없었다.
받는 값이 String, Int로 변경된 정도?
그리고 추가된 건 page값이 1 초과일 때와 1 이하일 때 호출하는 함수가 달라졌다는 것이다.
이 부분이 추가된 이유는 처음에 검색을 눌렀을 때는 새로운 검색어에 대한 값이 나와야하는데 기존 함수를 호출하니까 뒤에 붙기만 하는 걸 확인했기 때문이다.
그래서 스크롤을 해서 다음 페이지를 리사이클러뷰 리스트에 붙이는 것과 검색을 눌러서 새로운 데이터를 리스트에 초기화하는 함수를 따로 만들어 조건에 맞춰 호출하도록 했다.
if (page > 1) {
connectSearchResult(response)
} else {
registerSearchResult(response)
}
fun registerSearchResult(results: List<Document>) {
val sortedResults = results.toMutableList().sortedByDescending { result ->
when (result) {
is Document.ImageDocument -> result.dateTime
is Document.VideoDocument -> result.dateTime
}
}
_searchUiState.value = searchUiState.value?.copy(
searchResult = sortedResults
)
setSelectSearchResult()
}
fun connectSearchResult(results: List<Document>) {
val sortedResults = results.toMutableList().sortedByDescending { result ->
when (result) {
is Document.ImageDocument -> result.dateTime
is Document.VideoDocument -> result.dateTime
}
}
_searchUiState.value = searchUiState.value?.copy(
searchResult = searchUiState.value?.searchResult.orEmpty().toMutableList().apply {
addAll(sortedResults)
}
)
setSelectSearchResult()
}
이렇게 기능을 구현을 했는데 조금 아쉬웠던 건 정확도순으로 데이터를 가져오다보니 한 페이지를 정렬하고 넣고 한 페이지를 정렬하고 넣고를 할 때 날짜가 낮아지다가 높아지는 걸 반복한다는 것이다.
사실 이 부분을 해결하는 방법은 최신순으로 데이터를 가져오던가 처음부터 모든 페이지를 받아서 정렬을 하고 보여주는 것이라고 생각을 하는데 둘다 원하는 스타일은 아니다.
최신순으로 가져오면 정확도가 확실히 떨어지는걸 볼 수 있었고 모든 페이지 다 가져와서 정렬하는 건 시간이나 리사이클러뷰를 사용하는 의미가 떨어진다고 생각한다.
'Android > StoreInfo' 카테고리의 다른 글
<정리> 챌린지반 과제2 - 4 (2) | 2024.02.01 |
---|---|
<정리> 심화 개인과제 5 (0) | 2024.01.31 |
<정리> 심화 개인과제 3 (1) | 2024.01.29 |
<정리> 심화 개인과제 2 (1) | 2024.01.25 |
<정리> 챌린지반 과제2 - 3 (1) | 2024.01.25 |