I have an application which fetches movies from the moviedb api , i'm using paging library 3 to page the data , i have set up everything and data is showing properly , the only thing that is not working in loading states , upon reading little bit more about loading state adapter , i got to know that it only works when fetching data from db after using remote mediator , i might be wrong , someone please corrects me , i would appreciate any help ..
Code
val layoutManager = LinearLayoutManager(this)
binding.recyclerView.layoutManager = layoutManager
binding.recyclerView.setHasFixedSize(true)
movieAdapter = TrendingMovieAdapter(object : MovieListener{
override fun onMovieSelected(movieId: Int) {
Intent(this#MainActivity,DetailsActivity::class.java).apply {
putExtra("id",movieId)
startActivity(this)
}
}
})
binding.recyclerView.adapter = movieAdapter.withLoadStateHeaderAndFooter(
footer = LoaderAdapter(),
header = LoaderAdapter()
)
lifecycleScope.launch {
movieViewModel.getPagedTrendingMovies().collectLatest {
movieAdapter.submitData(it)
}
}
LoadStatesAdapter class
class LoaderAdapter : LoadStateAdapter<LoaderAdapter.ViewHolder>() {
inner class ViewHolder(var binding : LoaderItemBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): ViewHolder {
return ViewHolder(
DataBindingUtil.inflate(LayoutInflater.from(parent.context),
R.layout.loader_item,parent,false)
)
}
override fun onBindViewHolder(holder: ViewHolder, loadState: LoadState) {
holder.binding.progressBar.isVisible = loadState is LoadState.Loading
}
}
I also config footer load state adapter same your code, there's doesn't anything to be wrong. Hmm, you can try call executePendingBindings() after call holder.binding.progressBar.isVisible = loadState is LoadState.Loading.
This is onBindViewHolder() func:
override fun onBindViewHolder(holder: ViewHolder, loadState: LoadState) { holder.binding.progressBar.isVisible = loadState is LoadState.Loading executePendingBindings() }
Related
The problem: I haven't found a solution that works and allows me to use onLongClickListener in my recyclerview adapter
I understand that my options seem to be either implementing an interface or using a lambda, however I've been trying everything I can find and none of them are working.
places I have tried solutions from:
Item Onclick RecyclerView Kotlin Android
RecyclerView onClick in kotlin
How to add a click listener to my recycler view (Android kotlin)
RecyclerView itemClickListener in Kotlin
Recyclerview Card Item Onclick Kotlin
and everything else I could find when googling "kotlin recyclerview onClickAdapter"
I get different errors with every solution, but the main takeaway is that none of them are working, which tells me that there is probably a problem with my adapter code to begin with.
Example of one error I get: If I try to use setOnClickListener in the bind function of TaskViewHolder, I get the error of
Wrong return type, expecting Boolean, received Unit
There is no passed in list because I use submitList from a viewmodel with a room database
The adapter code is based off of the Room with a View code
I have
Adapter Code:
class TaskRvAdapter : ListAdapter<Task, TaskRvAdapter.TaskViewHolder>(TaskComparator()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
return TaskViewHolder.create(parent)
}
override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
val current = getItem(position)
holder.bind(current.task)
}
class TaskViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val taskItemView: TextView = itemView.findViewById(R.id.task_rv_item)
fun bind(text: String?) {
taskItemView.text = text
}
companion object {
fun create(parent: ViewGroup): TaskViewHolder {
val view: View = LayoutInflater.from(parent.context)
.inflate(R.layout.task_rv_item, parent, false)
return TaskViewHolder(view)
}
}
}
class TaskComparator : DiffUtil.ItemCallback<Task>() {
override fun areItemsTheSame(oldItem: Task, newItem: Task): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Task, newItem: Task): Boolean {
return oldItem.task == newItem.task
}
}
}
try with this code, properly formatted
fun bind(text: String?) {
taskItemView.text = text
itemView.setOnClickListener {
Log.i("TaskRvAdapter", "item clicked: "+text)
}
itemView.setOnLongClickListener{
Log.i("TaskRvAdapter", "item long clicked: "+text)
return#setOnLongClickListener true
}
}
I'm migrating from Paging 2 to Paging 3. The app stores a large dataset in a database using Room, and I can load the data from Room and display it okay. The issue I have is as soon as the app makes a change to the database, it crashes.
Code Snippets
IssueRepository
#Query("SELECT * FROM isssue WHERE project_id = ?")
fun findAllInProject(projectId:Int): PagingSource<Int, IssueListBean>
In the function onCreateView
val dataSource = DB.store.issueRepository().findAllInProject(project.id)
val pageConfig = PagingConfig(50)
val pager = Pager(pageConfig, null) { dataSource }.flow
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
pager.collectLatest { data ->
adapter.submitData(data)
}
}
class PagingAdapter : PagingDataAdapter<IssueListBean, PagingAdapter.ViewHolder>(EntityComparator()) {
inner class ViewHolder(private val adapterBinding: ItemIssueListBinding) : RecyclerView.ViewHolder(adapterBinding.root) {
fun bind(position: Int) {
val issueListBean = getItem(position)
adapterBinding.label.text = issueListBean.label
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemIssueListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
}
So when users tap on an item they can edit it. As soon the item is saved via Room the app crashes with the following exception:
java.lang.IllegalStateException: An instance of PagingSource was re-used when Pager expected to create a new
instance. Ensure that the pagingSourceFactory passed to Pager always returns a
new instance of PagingSource.
Am I using Paging 3 wrong? I can't find many articles online talk about using Room as data source where you make changes.
The lambda you pass to Pager() should return a new instance of the data source each time, so move the call to findAllInProject() into that lambda, like
val pager = Pager(pageConfig, null) {
DB.store.issueRepository().findAllInProject(project.id)
}.flow
I want to refresh my Recycler View, i receive my data by viewModel and pass it for my adapter
so i don’t know how to clear this data and call it again
MainActivity:
class MainActivity : AppCompatActivity() {
private val viewModel: ContatoViewModel = ContatoViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main);
configuraObserver()
viewModel.search()
}
private fun configuraObserver() {
viewModel.contato.observe(this, { data ->
Log.i("API", "Data received")
contato_recyclerview.apply {
layoutManager = LinearLayoutManager(this.context, LinearLayoutManager.VERTICAL, false)
adapter = ContatoAdapter(this.context, data.conteudoResposta)
}
})
}
My Adapter:
class ContatoAdapter(private val context: Context?, private val contatos : List<Contato>) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.list_item_contato,parent, false)
return ContatoViewHolder(view)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder as ContatoViewHolder
val contato = contatos.elementAt(position)
holder.bindView(contato)
}
override fun getItemCount(): Int {
return contatos.size
}
if you just want to refresh your data(which you already received) in the recycler view you just need to call notifyDataSetChanged() from your Adapter.
SwipeRefreshLayout is needed when you want to implement pull to refresh, which means you want to trigger the initiation of API call when someone pulls down the screen and then after receiving the data you will pass it to Adapter and notifyDataSetChanged()
For implementing pull to refresh you can follow this Google Doc
READ FIRST:
Apologies, it seems I have played myself. I was using RecyclerView in my xml earlier, but switched it over for CardStackView (it still uses the exact same RecyclerView adapter). If I switch back to RecyclerView, the original code below works - the scroll position is saved and restored automatically on configuration change.
I'm using a MVVM viewmodel class which successfully retains list data for a RecyclerView after a configuration change. However, the previous RecyclerView position is not restored. Is this expected behaviour? What would be a good way to solve this?
I saw a blog post on medium briefly mentioning you can preserve scroll position by setting the adapter data before setting said adapter on the RecyclerView.
From what I understand, after a configuration change the livedata that was being observed earlier gets a callback. That callback is where I set my adapter data. But it seems this callback happens after the onCreate() function finishes by which point my RecyclerView adapter is already set.
class MainActivity : AppCompatActivity() {
private val adapter = MovieAdapter()
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Create or retrieve viewmodel and observe data needed for recyclerview
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
viewModel.movies.observe(this, {
adapter.items = it
})
binding.recyclerview.adapter = adapter
// If viewmodel has no data for recyclerview, retrieve it
if (viewModel.movies.value == null) viewModel.retrieveMovies()
}
}
class MovieAdapter :
RecyclerView.Adapter<MovieAdapter.MovieViewHolder>() {
var items: List<Movie> by Delegates.observable(emptyList()) { _, _, _ ->
notifyDataSetChanged()
}
class MovieViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val binding = ItemMovieCardBinding.bind(itemView)
fun bind(item: Movie) {
with(binding) {
imagePoster.load(item.posterUrl)
textRating.text = item.rating.toString()
textDate.text = item.date
textOverview.text = item.overview
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_movie_card, parent, false)
return MovieViewHolder(view)
}
override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount() = items.size
}
class MainViewModel : ViewModel() {
private val _movies = MutableLiveData<List<Movie>>()
val movies: LiveData<List<Movie>> get() = _movies
fun retrieveMovies() {
viewModelScope.launch {
val client = ApiClient.create()
val result: Movies = withContext(Dispatchers.IO) { client.getPopularMovies() }
_movies.value = result.movies
}
}
}
Set adapter only after its items are available.
viewModel.movies.observe(this, {
adapter.items = it
binding.recyclerview.adapter = adapter
})
I'm able to retrieve data from Firestore (it is definetly available inside the GlobalScope of populateValletList() and I was able to populate my RecyclerView from an asnyc database call or when I simply added a Vallet to my items list manually inside populateValletList(), however, when I want to populate that View from Firestore data it doesn't work. The
onBindViewHolder in RecyclerAdapter doesn't get called anymore
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder){
is ValletViewHolder ->{
holder.bind(items[position])
}
}
}
that's how I populate my items private var items : MutableList<Vallet> = ArrayList() :
fun populateValletList() {
GlobalScope.launch {
items = getAllValletsFromDatabase.executeUseCase()
}
}
I init my RecyclerView inside onActivityCreated
private fun initRecyclerView(){
recycler_view_vallets.apply{
layoutManager = LinearLayoutManager(context)
addItemDecoration(ValletRecyclerAdapter.ValletItemDecorator(30))
valletAdapter = ValletRecyclerAdapter()
adapter = walletAdapter
}
valletAdapter.populateValletList()
}
This is my first time working with coroutines, what am I overlooking here?
Ok, I think there is some improvements that you could in your code, but the reason I believe is not working the way you want is because I you update your Items List, you have to call notifyDataSetChanged() on your adapter.
Ideally u should run ur coroutine using a scope that is not global, to avoid leaks, you can use a viewmodel for it, or you could use lifecycleScope.run { } in ur fragment, for that I believe you will need to add a depedency.
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
And regarding update your recycler, I recommend using ListAdapter, so it adds DIFF Utils and makes easier to update values.
To sum up.
Ur Recycler Adapter would be like this:
class HomePatchesAdapter : ListAdapter<Vallet, RecyclerView.ViewHolder>(REPO_COMPARATOR) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return HomePatchesViewHolder.create(parent, viewType)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val repoItem = getItem(position)
if (repoItem != null) {
(holder as HomePatchesViewHolder).bind(repoItem)
}
}
companion object {
private val REPO_COMPARATOR = object : DiffUtil.ItemCallback<Vallet>() {
override fun areItemsTheSame(oldItem: Vallet, newItem: Vallet): Boolean =
oldItem.name == newItem.name
override fun areContentsTheSame(oldItem: Vallet, newItem: Vallet): Boolean =
oldItem == newItem
}
}
}
and ur fragment would be like this:
private fun initRecyclerView(){
recycler_view_vallets.apply{
layoutManager = LinearLayoutManager(context)
addItemDecoration(ValletRecyclerAdapter.ValletItemDecorator(30))
}
val adapter = HomePatchesAdapter()
recycler_view_vallets?.adapter = adapter
lifecycleScope.run {
adapter.submitList(getAllValletsFromDatabase.executeUseCase())
}
}
Let me know if it makes sense.