Infinite scroll in the repository list

main
Clément FRÉVILLE 2 years ago
parent 1dd386c3d6
commit 35462dfe00

@ -3,39 +3,26 @@ package fr.uca.iut.clfreville2.teaiswarm
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentFactory
import androidx.fragment.app.commit
import fr.uca.iut.clfreville2.teaiswarm.fragment.RepositoryListFragment
import fr.uca.iut.clfreville2.teaiswarm.model.Repository
import fr.uca.iut.clfreville2.teaiswarm.network.GiteaService
import kotlinx.coroutines.launch
import kotlin.math.max
const val REPOSITORY_OWNER = "repository_owner"
const val REPOSITORY_NAME = "repository_name"
class MainActivity : AppCompatActivity() {
private val service = GiteaService()
private lateinit var repositories: RecyclerView
private lateinit var previousButton: Button
private lateinit var nextButton: Button
private var currentPage = 1
override fun onCreate(savedInstanceState: Bundle?) {
supportFragmentManager.fragmentFactory = RepositoryListFragmentFactory { repo ->
adapterOnClick(repo)
}
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
repositories = findViewById(R.id.repositories_view)
previousButton = findViewById(R.id.previous_repository_list)
nextButton = findViewById(R.id.next_repository_list)
updateList()
previousButton.setOnClickListener {
currentPage = max(currentPage - 1, 0)
updateList()
}
nextButton.setOnClickListener {
currentPage += 1
updateList()
supportFragmentManager.commit {
setReorderingAllowed(true)
replace(R.id.fragment_container_view, RepositoryListFragment::class.java, null)
}
}
@ -46,12 +33,12 @@ class MainActivity : AppCompatActivity() {
startActivity(intent)
}
private fun updateList() {
lifecycleScope.launch {
val repos = service.listActiveRepositories("clement.freville2", currentPage)
repositories.adapter = RepositoryListAdapter(repos) { repo ->
adapterOnClick(repo)
class RepositoryListFragmentFactory(private val onClick: (Repository) -> Unit) : FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
if (className == RepositoryListFragment::class.java.name) {
return RepositoryListFragment("clement.freville2", onClick);
}
return super.instantiate(classLoader, className)
}
}
}

@ -1,14 +1,17 @@
package fr.uca.iut.clfreville2.teaiswarm
package fr.uca.iut.clfreville2.teaiswarm.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import fr.uca.iut.clfreville2.teaiswarm.R
import fr.uca.iut.clfreville2.teaiswarm.model.Repository
class RepositoryListAdapter(private val dataSet: List<Repository>, private val onClick: (Repository) -> Unit) :
RecyclerView.Adapter<RepositoryListAdapter.ViewHolder>() {
class RepositoryListAdapter(diffCallback: DiffUtil.ItemCallback<Repository>, private val onClick: (Repository) -> Unit) :
PagingDataAdapter<Repository, RepositoryListAdapter.ViewHolder>(diffCallback) {
class ViewHolder(view: View, private val onClick: (Repository) -> Unit) : RecyclerView.ViewHolder(view) {
private val repositoryNameView: TextView
@ -72,8 +75,14 @@ class RepositoryListAdapter(private val dataSet: List<Repository>, private val o
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(dataSet[position])
holder.bind(getItem(position))
}
override fun getItemCount() = dataSet.size
object RepositoryComparator : DiffUtil.ItemCallback<Repository>() {
override fun areItemsTheSame(oldItem: Repository, newItem: Repository): Boolean =
oldItem.owner == newItem.owner && oldItem.name == newItem.name
override fun areContentsTheSame(oldItem: Repository, newItem: Repository): Boolean =
oldItem == newItem
}
}

@ -0,0 +1,104 @@
package fr.uca.iut.clfreville2.teaiswarm.fragment
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import androidx.paging.*
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.uca.iut.clfreville2.teaiswarm.R
import fr.uca.iut.clfreville2.teaiswarm.adapter.RepositoryListAdapter
import fr.uca.iut.clfreville2.teaiswarm.model.Repository
import fr.uca.iut.clfreville2.teaiswarm.network.GiteaService
import fr.uca.iut.clfreville2.teaiswarm.network.RepositoryService
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
class RepositoryListFragment(private val username: String, private val onClick: (Repository) -> Unit) : Fragment(R.layout.repository_list) {
private val service = GiteaService()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
updateRepositories()
}
private fun updateRepositories() {
val viewModel by viewModels<RepositoryViewModel>(
factoryProducer = {
RepositoryViewModelFactory(
service,
username
)
}
)
val pagingAdapter =
RepositoryListAdapter(RepositoryListAdapter.RepositoryComparator, onClick)
val recyclerView = requireView().findViewById<RecyclerView>(R.id.repositories_view)
recyclerView.adapter = pagingAdapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
viewLifecycleOwner.lifecycleScope.launch {
viewModel.flow.collectLatest { pagingData ->
pagingAdapter.submitData(pagingData)
}
}
}
class RepositorySource(
private val service: RepositoryService,
private val username: String
) : PagingSource<Int, Repository>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repository> =
try {
val nextPageNumber = params.key ?: 1
val response = service.listActiveRepositories(username, nextPageNumber)
LoadResult.Page(
data = response,
prevKey = nextPageNumber - 1,
nextKey = nextPageNumber + 1
)
} catch (e: IOException) {
LoadResult.Error(e)
} catch (e: HttpException) {
LoadResult.Error(e)
}
override fun getRefreshKey(state: PagingState<Int, Repository>): Int? =
state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
class RepositoryViewModel(
private val service: RepositoryService,
private val username: String
) : ViewModel() {
val flow = Pager(
PagingConfig(pageSize = 10, enablePlaceholders = true)
) {
RepositorySource(service, username)
}.flow.cachedIn(viewModelScope)
}
class RepositoryViewModelFactory(
private val service: RepositoryService,
private val username: String
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(RepositoryViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return RepositoryViewModel(service, username) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
}

@ -1,7 +1,6 @@
package fr.uca.iut.clfreville2.teaiswarm.network
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapters.EnumJsonAdapter
import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import fr.uca.iut.clfreville2.teaiswarm.model.*
@ -33,7 +32,11 @@ class GiteaService(private val handle: GiteaApiService) : RepositoryService {
constructor() : this(createRetrofit().create(GiteaApiService::class.java))
override suspend fun listActiveRepositories(username: String, page: Int): List<Repository> = withContext(Dispatchers.IO) {
handle.listActiveRepositories(username, page)
if (page < 1) {
emptyList()
} else {
handle.listActiveRepositories(username, page)
}
}
override suspend fun listCommits(

@ -1,32 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/repositories_view"
<androidx.fragment.app.FragmentContainerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/previous_repository_list"
android:text="@string/previous" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/next_repository_list"
android:text="@string/next" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
android:id="@+id/fragment_container_view" />
</LinearLayout>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/repositories_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Loading…
Cancel
Save