I've got an RecyclerView that lists articles, in each article/item in the onBindViewHolder I'm loading another RecyclerView for the comments and I need to update them when the user returns to the activity.
Is there a way to detect the activity OnResume inside onBindViewHolder of the parent RecyclerView?
I assume that you have some data class for article which holds its details and comments. You pass this list to first recycler which in onBind is filling comment recyclers with data. If you have to update comments just pass list of articles with updated comments to article's adapter. It will trigger onBindViewHolder inside which you will have new comments. In code it would be somethings like this:
data class Article (
val name: String
... other details
val comments: List<Comment>
)
onBindViewHolder for Article's adapter:
override fun onBindViewHolder(holder: Holder, position: Int) {
val article = getItem(position)
// here you should submit list of comments
commentsAdapter.submitList(article.comments)
}
and in Activity:
override fun onResume() {
super.onResume()
//get new comments here and set it for new articles
val newArticles = oldArticles.map { article -> it.copy(comments = newComments)}
articlesAdapter.submitList(newArticles)
}
By the way it seems like AdapterDelegates would be even better solution to your problem. Read more here: http://hannesdorfmann.com/android/adapter-delegates
Related
I have succeeded pulling JSON from Reddit API but I cannot put it into my RecyclerView. I set a Log to check if my JSON is empty or null, and the Log successfully print the desired output, so that means my JSON is not empty and contains the necessary data.
Here is my PostRowAdapter.kt
class PostRowAdapter(private val viewModel: MainViewModel)
: ListAdapter<RedditPost, PostRowAdapter.VH>(RedditDiff()) {
private var awwRow = listOf<RedditPost>()
override fun onBindViewHolder(holder: VH, position: Int) {
val binding = holder.binding
awwRow[position].let{
binding.title.text = it.title
}
}
override fun getItemCount() = awwRow.size
}
I thought my code was correct, but when I ran the app, the RecyclerView still blank. Where am I wrong?
your PostRowAdapter is populating awwRow list, which is empty on start and never updated, thus this RecyclerView will always contain 0 elements
if you are using ListAdapter and submitList(...) method then you shouldn't override getItemCount and you shouldn't have own data list, so remove these lines
private var awwRow = listOf<RedditPost>() // on top
override fun getItemCount() = awwRow.size // on bottom
if you want access to whole list set with submitList() method then you can call inside adapter getCurrentList(). if you need a particular item at position (like in onBindViewHolder) then use getItem(position). So instead of
awwRow[position].let{
you should have
getItem(position).let{
I have a functioning RecyclerView which works fine with the given data I provide via ListAdapter as shown below. What I now want is to add additional data to my list items.
class IngredientAdapter(
private val ingredientClickListener: IngredientClickListener
) : ListAdapter<Ingredient, RecyclerView.ViewHolder>(EventDiffCallback()) {
private val adapterScope = CoroutineScope(Dispatchers.Default)
fun submitIngredientsList(list: List<Ingredient>?) {
adapterScope.launch {
withContext(Dispatchers.Main) {
submitList(list)
}
}
}
I have no idea how to do that properly or if RecyclerViews are even capable of doing this. The only way I am able to see is merging both data classes (Ingredient plus the new one) together and submit them as list together to the adapter but this seems messy and I am looking for a better way.
So my question is: How to feed data into my list items without merging it together with the data I already have? Is RecyclerView the wrong choice in my case?
Thanks in advance!
Ok I found a solution: I submitted the additional data list just how like the other one but did not attach it directly to the ListAdapter since this is not possible.
In the function onBindViewHolder after getting the item for the current position I use this information to retrieve the correct element from the new data list. Then I attach the data to the view by calling using the viewholders view binding
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ViewHolder -> {
val resources = holder.itemView.context.resources
val ingredientItem = getItem(position)
holder.bind(ingredientClickListener, ingredientItem)
val groceryInStock: GroceryInStock? = availableGroceries.firstOrNull{
ingredientItem.grocery.groceryId == it.grocery.groceryId
}
holder.binding.listItemAvailableAmount.text = groceryInStock.amount
}
}
}
Since the data I add fully depends on the already existing item being displayed I did not make any changes to the functions areItemsTheSame and areContentsTheSame in my overriden DiffUtil.ItemCallback class.
I have an app structured in MVVM. I have different fragments within the same activity. Each fragment has its own ViewModel and all data are retrieved from a REST API.
In FragmentA, there is a RecyclerView that lists X class instances. I want to set OnClickListener on the RecyclerView and I want to pass related X object to FragmentB when an item clicked in the RecyclerView. How can I achieve this?
How I imagine it is the following.
The Fragment passes a listener object to the adapter, which in turn passes it to the ViewHolders
Here is a quick sketch of how it should look like
class Fragment {
val listener = object: CustomAdapter.CustomViewHolderListener() {
override fun onCustomItemClicked(x: Object) {}
}
fun onViewCreated() {
val adapter = CustomAdapter(listener)
}
}
---------------
class CustomAdapter(private val listener: CustomViewHolderListener) {
val listOfXObject = emptyList() // this is where you save your x objects
interface CustomViewHolderListener{
fun onCustomItemClicked(x : Object)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.itemView.setOnClickListener {
listener.onCustomItemClicked(listOfXObject[position])
}
}
}
Here are some articles that might help you get the general gist of the things.
They don't answer your question directly though
Hope it is helpful
link 1 link 2
if you're using data binding you need to pass your view(which is Fragment in your case) into the layout via adapter class and you need to import your view in layout file to be able to call view's method
android:onClick="#{() -> view.onXXXClick(item)}"
pass your current model class which is item into this new method and then create onXXXClick method in your view and do whatever you wish.
if you will be doing view related operations such as navigation from one fragment to another or starting a service you should create above function in your view, if you're doing network or db related operations it should be in your ViewModel
you can check out my GitHub repository to understand better.
I am using FirestorePagingAdapter to load data from firestore and display them in recyclerView.
I get the item and bind it to the view.
override fun onBindViewHolder(holder: MyViewHolder, position: Int, model: Question) {
holder.bind(model)
}
fun bind(question: Question) {
//here I load questionData in my viewholder
}
I set onClickListener to textViews in my viewHolder, my question model has up to 5 options which are shown in my viewHolder, so when user presses one I want to update data to the model
override fun onClick(v: View) {
val pressedQuestion = getItem(adapterPosition)?.toObject(Question::class.java)
}
Inside clickListener I get pressed question by calling getItem and toObject.
There I want to update field pressedQuestion.questionId = "answered"
But when this view is scrolled of the screen and than back on sreen data doesn't seem to be changed, questionId field remains the same even though I set it to "answered".
I tried calling every version of notifyDatasetChanged method but it doesn't work.
How can I change data that FirestorePagingAdapter is loading in my viewHolder from inside onClickListener
I have an issue with my RecyclerView and ListAdapter.
Through an API, I receive items in a ascending order from older to newer.
My list is then being refreshed by calling the submitList(items) method.
However, since everything is asynchronous, after receiving the first item, the RecyclerView remains on the position of the first item received and showed.
Since in the ListAdapter class there is no callback when the submitList() method completed, I cannot find a way to scroll after the updates to one of the new items that has been added.
Is there a way to intercept when ListAdapter has been updated ?
Kotlin : Very simple method to do this.
listAdapter.submitList(yourNewList) {
yourRecyclerView.scrollToPosition(0)
}
Not specific to ListAdapter, but it should work nonetheless:
Just use adapter.registerAdapterDataObserver(RecyclerView.AdapterDataObserver) and override the relevant onItemXXX method to tell your RecyclerView to scroll to whatever position.
As an example:
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
(recycler_view.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(positionStart, 0)
}
})
I have checked ListAdapter source code and I see that there is onCurrentListChanged method.
You can simply override it in your custom adapter. You can also pass a listener to the adapter to catch the event and scroll your RecyclerView to the beginning.
Your adapter:
typealias ListChangeListener = () -> Unit
class Adapter(private val listChangeListener: ListChangeListener) : ListAdapter<Item, Adapter.ViewHolder>(Callback) {
override fun onCurrentListChanged(previousList: List<Item>, currentList: List<Item>) {
super.onCurrentListChanged(previousList, currentList)
// Call your listener here
listChangeListener()
}
// Other methods and ViewHolder class
}
Create the adapter in your Activity or Fragment:
recyclerView.adapter = Adapter { recyclerView.scrollToPosition(0) }
The method to override is called:
public void onCurrentListChanged(List<T> previousList, List<T> currentList)
As the documentation reads:
Called when the current List is updated.
The inline documentation of the ListAdapter reads:
This class is a convenience wrapper around AsyncListDiffer that implements Adapter common default behavior for item access and counting.
And later on it reads:
Advanced users that wish for more control over adapter behavior, or to provide a specific base class should refer to AsyncListDiffer, which provides custom mapping from diff events to adapter positions.
This means, you'd need to extend the regular RecyclerView.Adapter, combined with a custom implementation of the AsyncListDiffer; and not the ListAdapter (which is just a convenience wrapper, with the stated basic functionality).
One can use the ListAdapter class as a template for that (not extending it is the clue), but the resulting class can then be extended and reused. else there is no chance to get control over the desired callback.
For my case, just override onCurrentListChanged in your adapter class and call notifyDataSetChanged()
override fun onCurrentListChanged(
previousList: MutableList<InboxMessage>,
currentList: MutableList<InboxMessage>
) {
super.onCurrentListChanged(previousList, currentList)
notifyDataSetChanged()
}
listAdapter.submitList(yourNewList) { yourRecyclerView.smoothScrollToPosition(0) }