Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 홈 화면 무한 스크롤 기능 구현 #109

Merged
merged 2 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading