Skip to content

Commit

Permalink
feat: 홈 화면 무한 스크롤 기능 구현 (#109)
Browse files Browse the repository at this point in the history
* build: pagination라이브러리 추가

* feat: 홈 화면 무한 스크롤 기능 구현
  • Loading branch information
Namyunsuk authored and ChooSeoyeon committed Oct 11, 2024
1 parent b0b5ccc commit ddc2d6a
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 24 deletions.
4 changes: 4 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ dependencies {
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation("androidx.test:runner:1.4.0")

// Espresso RecyclerView Actions
androidTestImplementation("androidx.test.espresso:espresso-contrib:3.3.0")

// Pagination
implementation("androidx.paging:paging-runtime-ktx:3.3.0")
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ class OfferingsRepositoryImpl(
override suspend fun fetchOfferings(
lastOfferingId: Long,
pageSize: Int,
): Result<List<Offering>> {
): List<Offering> {
return offeringsDataSource.fetchOfferings(lastOfferingId, pageSize).mapCatching {
it.offerings.map { it.toDomain() }
}
}.getOrThrow()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.zzang.chongdae.domain.paging

import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.zzang.chongdae.domain.model.Offering

class OfferingPagingSource(
private val fetchOfferings: suspend (lastOfferingId: Long, pageSize: Int) -> List<Offering>,
) : PagingSource<Long, Offering>() {
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, Offering> {
val lastOfferingId = params.key ?: INIT_LAST_OFFERING_ID
return runCatching {
val offerings = fetchOfferings(lastOfferingId, params.loadSize)
LoadResult.Page(
data = offerings,
prevKey = if (lastOfferingId == INIT_LAST_OFFERING_ID) null else lastOfferingId - params.loadSize,
nextKey = if (offerings.isEmpty()) null else lastOfferingId + params.loadSize,
)
}.onFailure { throwable ->
LoadResult.Error<Long, Offering>(throwable)
}.getOrThrow()
}

override fun getRefreshKey(state: PagingState<Long, Offering>): Long? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey
}
}

companion object {
private const val INIT_LAST_OFFERING_ID = 0L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ interface OfferingsRepository {
suspend fun fetchOfferings(
lastOfferingId: Long,
pageSize: Int,
): Result<List<Offering>>
): List<Offering>
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,10 @@ class HomeFragment : Fragment(), OnArticleClickListener {

private fun setUpOfferingsObserve() {
viewModel.offerings.observe(viewLifecycleOwner) {
offeringAdapter.submitList(it)
offeringAdapter.submitData(viewLifecycleOwner.lifecycle, it)
}
}

override fun onStart() {
super.onStart()
viewModel.updateArticles()
}

override fun onClick(offeringId: Long) {
OfferingDetailActivity.startActivity(activity as Context, offeringId)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
package com.zzang.chongdae.presentation.view.home

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.liveData
import com.zzang.chongdae.domain.model.Offering
import com.zzang.chongdae.domain.paging.OfferingPagingSource
import com.zzang.chongdae.domain.repository.OfferingsRepository
import kotlinx.coroutines.launch

class OfferingViewModel(
private val offeringsRepository: OfferingsRepository,
) : ViewModel() {
private val _offerings: MutableLiveData<List<Offering>> = MutableLiveData()
val offerings: LiveData<List<Offering>> get() = _offerings
val offerings: LiveData<PagingData<Offering>> =
Pager(
config = PagingConfig(pageSize = PAGE_SIZE, enablePlaceholders = false),
pagingSourceFactory = { OfferingPagingSource(fetchOfferings = offeringsRepository::fetchOfferings) },
).liveData
.cachedIn(viewModelScope)

// 무한 스크롤 적용 시 수정 예정
fun updateArticles() {
viewModelScope.launch {
offeringsRepository.fetchOfferings(0L, 100).onSuccess {
_offerings.value = it
}.onFailure {
}
}
companion object {
private const val PAGE_SIZE = 10
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package com.zzang.chongdae.presentation.view.home.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import com.zzang.chongdae.databinding.ItemOfferingBinding
import com.zzang.chongdae.domain.model.Offering
import com.zzang.chongdae.presentation.view.home.OnArticleClickListener

class OfferingAdapter(
private val onArticleClickListener: OnArticleClickListener,
) : ListAdapter<Offering, OfferingViewHolder>(productComparator) {
) : PagingDataAdapter<Offering, OfferingViewHolder>(productComparator) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,
Expand All @@ -24,7 +24,7 @@ class OfferingAdapter(
holder: OfferingViewHolder,
position: Int,
) {
holder.bind(getItem(position), onArticleClickListener)
getItem(position)?.let { holder.bind(it, onArticleClickListener) }
}

companion object {
Expand Down

0 comments on commit ddc2d6a

Please sign in to comment.