1. 무한 스크롤
무한 스크롤을 구현하기 위해서 addOnScrollListener로 스크롤을 감지해야한다.
homeRvVideoList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val isAtEndOfList = visibleItemCount + firstVisibleItemPosition >= totalItemCount
// !homeRvVideoList.canScrollHorizontally(1)
if (isAtEndOfList) {
getNextPage() // 주석 시 홈 api 사용 x
}
}
})
여기서 주석처리한 .canScrollHoriznotally(1)은 스크롤이 맨 마지막에 도착했는지에 대한 판정을 해주는 식이었으나
해당 값을 사용하면 에러 발생하였다.
추정하기론, 스크롤 이벤트는 굉장히 민감하고 맨 마지막 스크롤이 여러번 되면서 굉장히 많은 호출이 되었던 것 같다.
이는 isAtendOfList라는 bool식을 만들어 준 이후에도 그러했다.
다음 페이지를 호출하는 함수
fragment
private fun getNextPage() = with(viewModel) {
viewModel.setNextPage()
}
viewmodel
private val _categoryList: MutableLiveData<Set<PlayListModel>> = MutableLiveData()
val categoryList: LiveData<Set<PlayListModel>> get() = _categoryList
~~~~~~
기존코드
~~~~~~
fun setNextPage() {
viewModelScope.launch {
val token = pageToken.value
val response = repository.getPopularVideo(token)
val list = response.first
val nextToken = response.second
val currentList = categoryList.value.orEmpty().toMutableSet().apply {
addAll(list)
}
_pageToken.value = nextToken
_categoryList.value = currentList
}
}
1) 여려번 호출되는 문제를 해결하기 위해 자료구조형 'set'을 사용함
set은 중복을 알아서 처리해주는 집합형 자료구조형이다.
따라서 여러번 같은 값이 들어오는 것이 문제라면 set형으로 해당 방식을 막을 수 있다.
여기서 발생하는 문제
-> 근본적인 문제인 스크롤 이벤트가 예민하게 감지될 때 마다 api가 호출되는 것은 막지 못함.
2) 여러번 호출 할 수 없도록 state를 만들어서 관리하자!
viewmodel
private val _loading: MutableLiveData<Boolean> = MutableLiveData()
val loading: LiveData<Boolean> get() = _loading
fun setNextPage() {
viewModelScope.launch {
if (_loading.value == true) return@launch
_loading.value = true
val token = pageToken.value
val response = repository.getPopularVideo(token)
val list = response.first
val nextToken = response.second
val currentList = categoryList.value.orEmpty().toMutableSet().apply {
addAll(list)
}
_pageToken.value = nextToken
_categoryList.value = currentList
_loading.value = false
}
}
loading이라는 bool식을 만들어서 첫 실행 시 loading.value를 true(로딩중이다)로 만든다.
이후 loading이 끝난 맨 마지막에 해당 값을 false로 세팅해준다.
그렇다면! 처음에 loading의 값이 true라면 return하여 다시 재실행할 수 없도록 만들어준다!
이로써 로딩이 구현되었으며 해당 값이 호출됨으로써 로딩의 true-false간격에 로딩 화면을 구상할 수 있게 되었다.
2. 자바 라이브러리의 null 체크
fragment에서
private fun initViewModel() {
with(viewModel) {
categoryList.observe(viewLifecycleOwner) {
if (it != null) {
videoAdapter.submitList(it.toList())
Log.d("리스폰", categoryList.value.toString())
}
}
pageToken.observe(viewLifecycleOwner) {
if (it != null) {
Log.d("토큰", pageToken.value.toString())
//호출 횟수 테스트용 observe
}
}
loading.observe(viewLifecycleOwner) {
if (it != null) { // 뷰 모델이 자바로 구성되어 있기 때문. 그런 경우 방어 코딩을 해줘야함.
if (it == true) {
//로딩 화면을 구현할 예정
} else {
//로딩 화면을 지우는 로직 구성 해야함
}
}
}
}
}
뷰 모델 사용 시 observe해당 람다식으로 표현될 수 있는 it들에 대한 null처리를 해줘야 한다.
그렇다면 왜 해줘야할 까?

뷰 모델의 안으로 들어가면 해당 코드가 java로 작성되었음을 알 수 있다.
java는 kotlin과 다르게 언제든지 null이 들어올 수 있으므로, java로 작성된 라이브러리를 사용할 때는 해당 값에 null이 들어오는지 체크할 수 있는 방어 코딩이 필요한 것이다!
이는 현재 쓰고있는 retrofit,glide에도 똑같이 적용 해야할 것 이다.
뷰 모델을 제외하고는 지금까지 적용하고는 있었지만, 이게 자바 코드라서가 아니라 구조 자체가 null이 들어오면 오류가 발생하는 구조였기 때문이다.
비슷한 이유지만 느낌적 느낌이 살짝 다르다.
정리하자면
java로 작성된 라이브러는 null값이 변수 선언 시 그냥 들어올 수 있다
-> kotlin은 ?체크를 해줘야 들어올 수 있다
-> 그렇다면 null체크를 함으로써 kotlin 문법상 충돌을 방지한며, null을 컨트롤 하기 편하게 만든다.
=> null safety하다
null이 들어와서는 안되는 기능에 null이 들어와서 앱이 죽었다.
->그렇다면 방어 코드를 작성해서 방어를 해준다
이런 흐름인 것이다.
'개발노트 > Kotlin' 카테고리의 다른 글
파이널프로젝트 1주차 7 firebase+viewmodel/livedata (0) | 2023.10.15 |
---|---|
파이널 프로젝트 1주차 4일 room database (0) | 2023.10.12 |
12주차 1일 horizontal recyclerview (0) | 2023.09.25 |
11주차 5일차 리포지토리 (1) | 2023.09.22 |
11주차 3일 sharedprfernce (0) | 2023.09.20 |