The notifyItemRangeInserted command isn't working - android

I'm having a problem when call the notifyItemRangeInserted of the adapter. When I call this method, nothing happens, simple as that. I've tried to set some println() in the ViewHolderAdapter, but he isn't called, so I can't view the prints.
I've tried all of the "notify" commands of the adapter, and none of these work. Simply nothing happens.
That's my MainActivity. All the objects and arrays I've tested, all of them are working like a charm. I can't understand why the notify doesn't work.
class MainActivity:AppCompatActivity(){
//Declarations of the variables
var pageNumber = 1
var limitPerPage = 5
lateinit var product: Product
var productList = ArrayList<EachProduct>()
var myAdapter =ViewHolderAdapter(productList, productList.size)
override fun onCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView.layoutManager = LinearLayoutManager(this#MainActivity)
recyclerView.adapter = myAdapter
The code to add items on the list and notify the ViewHolderAdapter is
//update the product list
fun updateProductList(product:Product){
for(i in 0 until 5 step 1){
productList.add(product.produtos[i])
}
showData(productList,pageNumber*limitPerPage)//then notify
}
fun showData(productList:List<EachProduct>,productsListSize:Int){
myAdapter.notifyItemRangeInserted(0,productList.size)
}
That's my ViewHolderAdapter class
class ViewHolderAdapter(private var products: List<EachProduct>, private val productsListSize: Int): RecyclerView.Adapter<ViewHolderAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent:ViewGroup,viewType:Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_layout, parent, false)
returnViewHolder(view)
}
override fun getItemCount() = productsListSize
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.productName.text=products[position].nome
Picasso.get().load(products[position].fabricante.img).into((holder.productLogo))
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val productName:TextView=itemView.ProductName
var productLogo:ImageView=itemView.ProductLogo
}
}
I expect the ViewHolderAdapter to be called, but this is not occurring. Why is that happens? I can't understand. I'll be very grateful if someone could help me.

Because initial value of the variable productsListSize is zero. Remove it from the constructor and change adapter like this:
class ViewHolderAdapter(private var products: List<EachProduct>): RecyclerView.Adapter<ViewHolderAdapter.ViewHolder>() {
override fun getItemCount() = products.size
}

A reason can be that the initial size of the item list you want to show is 0 and the recycler view height is set to wrap content. At the moment, for this case I see 2 options:
Keep wrap content for recycler view and make sure the initial list size > 0.
Set the height of the recycler view to match_parent or a fixed size and notifyItemRangeInserted will work without issues.

Related

Can i use ViewModelProvider and Observe in RecyclerView.Adapter?

I user MVVM and RecyclerView in this app so the recycle view show the list perfectly but when i add the view model to adapter i get an error in the logcat
Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.
i am new in this MVVM and i know is this possible or is any other way to do this
this is my adapter class with the viewHolder
class KeefAdapter : RecyclerView.Adapter<KeefViewHolder>() {
var dataOfAllKeef = listOf<String>()
init {
dataOfAllKeef = arrayListOf("Marijuwana" , "Bango" , "Weed" , "Hash")
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): KeefViewHolder {
lateinit var binding: KeefSingleItemBinding
binding = DataBindingUtil.inflate(LayoutInflater.from(parent.context) , R.layout.keef_single_item , parent , false)
val viewModel:OrderYourKeefViewModel = ViewModelProvider(OrderYourKeef()).get(OrderYourKeefViewModel::class.java)
binding.orderViewModelWithSingle = viewModel
viewModel.count.observe(OrderYourKeef(), Observer { newCountOfHash->
binding.root.theCountOfHash.text = newCountOfHash.toString()
})
return KeefViewHolder(binding.root)
}
override fun getItemCount() = dataOfAllKeef.size
override fun onBindViewHolder(holder: KeefViewHolder, position: Int) {
val item = dataOfAllKeef[position]
holder.keefName.text = item
if (item.equals("Marijuwana")) {
holder.keefImage.setImageResource(R.mipmap.marijuana)
} else if (item.equals("Bango")) {
holder.keefImage.setImageResource(R.mipmap.bango)
} else if (item.equals("Weed")) {
holder.keefImage.setImageResource(R.mipmap.weed)
} else if (item.equals("Hash")) {
holder.keefImage.setImageResource(R.mipmap.hashesh)
}
}
}
class KeefViewHolder(itemView:View) : RecyclerView.ViewHolder(itemView) {
var keefName:TextView = itemView.keefName
var keefImage: ImageView = itemView.keefImage
var increase: Button = itemView.increaseTheCount
var decrease: Button = itemView.minusTheCount
var theCountOfKeef: TextView = itemView.theCountOfHash
}
I think this is not the correct way to implement the MVVM pattern.
You have to call the viewModel = ViewModelProviders in your Activity. And after fetching the list items, pass it to your adapter and call the notifyDataSetChanged():
updateListItems(newListItems: List<YourItem>) {
currentItems = newListItems
notifyDataSetChanged()
}
Read more about it here
Adapter seems to be designed to be used rather passively than actively.
In OP's code, he would observe and get newCountOfHash in onCreateViewHolder to set it to binding.root.theCountOfHash.text. So this is a case that Adapter would actively seek and grab a value.
To avoid this 'active' Adapter, we should define Adapter behaving passively. Locally define countOfHash as Adapter's field value. The Adapter shouldn't mind countOfHash is LiveData or not. It just looks the field value.
class KeefAdapter : RecyclerView.Adapter<KeefViewHolder>() {
var countOfHash
override fun onBindViewHolder(holder: KeefViewHolder, position: Int) {
// You should not do this in onCreateViewHolder
// because that is done only once on creation time.
// (not invoked later again)
binding.root.theCountOfHash.text = countOfHash
}
}
Then outside of the Adapter, from Activity or Fragment that holds the Adapter, you may update Adapter.countOfHash with an Observer:
val viewModel:OrderYourKeefViewModel
= ViewModelProvider(OrderYourKeef()).get(OrderYourKeefViewModel::class.java)
viewModel.count.observe(OrderYourKeef(), Observer { newCountOfHash ->
Adapter.countOfHash = newCountOfHash.toString()
})
(Note: I'm not using Kotlin actively, there may be some syntax mistakes)

notifyDataSetChanged() not updating UI

I want to display new list of items retrieved from ViewModel in custom RecyclerView.Adapter. To do so I pass the retrieved list to adapter and invoke notifyDataSetChanged(), but nothing changes on the UI.
I've debugged code and adapter's list had 1 element (UI displayed 1 element), new retrieved list had 2 elements - after setting new list in adapter and invoking notifyDataSetChanged() UI didn't change, but should append that 1 element.
Fragment's code:
trip_details_participantsList.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
trip_details_participantsList.adapter = userBriefAdapter
tripsDetailsViewModel.getTripParticipants(tripId).observe(this, Observer {participants ->
tripsDetailsViewModel.getUsersBriefs(participants.map { x -> x.userId }).observe(this, Observer{ users ->
userBriefAdapter.setData(users)
})
})
Custom Adapter and ViewHolder:
class UserBriefAdapter : RecyclerView.Adapter<UserBriefViewHolder>() {
private var users: List<UserBrief> = mutableListOf()
fun setData(items: List<UserBrief>){
this.users = items
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: UserBriefViewHolder, position: Int) =
holder.bind(users[position])
override fun getItemCount() = users.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserBriefViewHolder {
val inflater = LayoutInflater.from(parent.context)
return UserBriefViewHolder(inflater, parent)
}
}
class UserBriefViewHolder(inflater: LayoutInflater, parent: ViewGroup) : RecyclerView.ViewHolder(inflater.inflate(
R.layout.trip_details_list_item, parent, false)) {
private var profilePictureImageView: ImageView = itemView.findViewById(R.id.trips_details_profile_image)
fun bind(userBrief: UserBrief){
Picasso.get()
.load(userBrief.profilePictureUrl)
.into(profilePictureImageView)
}
}
I've read existing topics about it, but every accepted solution is something like my adapter's setData() function with notifyDataSetChanged(). I've also tried to set new instance of UserBriefAdapter to RecyclerView each time observed values changed, but results were the same. When I go back and forth the view, then it correctly displays the elements, but I want to achieve this without changing the view.
It could be an issue of threading. Try to post a runnable to the main thread in your observer callback:
tripsDetailsViewModel.getUsersBriefs(participants.map { x -> x.userId }).observe(this, Observer{ users ->
view?.post { userBriefAdapter.setData(users) }
})
Done some more debugging and figured out that notifyDataSetChanged() wasn't the problem. I was passing wrong profilePictureUrl to UserBriefViewHolder, so Picasso library doesn't fetch an image and in consequence UI wasn't updated.

Saving the instance of Recycler view during orientation change

I have a RecyclerView which was build using an Arraylist. That Arraylist consists of User defined objects named ListItem.
Each recyclerview has a card view. Each CardView holds each ListItem.
I have removed one CardView from that RecyclerView.
When I rotate the screen , A new Activity is created which results in showing the old data. But I want the recyclerview to hold only updated list and should retain the scrolled position.
ListItem class :
class ListItem(var title: String, var info: String, val imageResource: Int) {
}
MainActivity class :
class MainActivity : AppCompatActivity() {
private lateinit var mSportsData: ArrayList<ListItem>
private lateinit var mAdapter: MyAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val gridColumnCount = resources.getInteger(R.integer.grid_column_count)
recycler_view.layoutManager = GridLayoutManager(this,gridColumnCount)
mSportsData = ArrayList()
recycler_view.setHasFixedSize(true)
initializeData()
recycler_view.adapter = mAdapter
var swipeDirs = 0
if (gridColumnCount <= 1) {
swipeDirs = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
}
val helper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT or ItemTouchHelper.UP or ItemTouchHelper.DOWN,swipeDirs) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
val from = viewHolder.adapterPosition
val to = target.adapterPosition
Collections.swap(mSportsData,from,to)
mAdapter.notifyItemMoved(from,to)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
mSportsData.removeAt(viewHolder.adapterPosition)
mAdapter.notifyItemRemoved(viewHolder.adapterPosition)
}
})
helper.attachToRecyclerView(recycler_view)
}
private fun initializeData() {
val sportsList : Array<String> = resources.getStringArray(R.array.sports_titles)
Log.d("Printing","$sportsList")
val sportsInfo : Array<String> = resources.getStringArray(R.array.sports_info)
val sportsImageResources : TypedArray = resources.obtainTypedArray(R.array.sports_images)
mSportsData.clear()
for (i in sportsList.indices-1) {
Log.d("Printing","${sportsList[i]},${sportsInfo[i]},${sportsImageResources.getResourceId(i,0)}")
mSportsData.add(ListItem(sportsList[i], sportsInfo[i], sportsImageResources.getResourceId(i, 0)))
}
sportsImageResources.recycle()
mAdapter = MyAdapter(mSportsData,this)
mAdapter.notifyDataSetChanged()
}
fun resetSports(view: View) {
initializeData()
}
}
MyAdapter class :
class MyAdapter(var mSportsData: ArrayList<ListItem>, var context: Context) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.wordlist_item,parent,false))
}
override fun getItemCount() = mSportsData.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val listItem = mSportsData.get(position)
holder.bindTo(listItem)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
override fun onClick(view: View) {
val currentSport = mSportsData.get(adapterPosition)
val detailIntent = Intent(context, DetailActivity::class.java)
detailIntent.putExtra("title", currentSport.title)
detailIntent.putExtra("image_resource", currentSport.imageResource)
context.startActivity(detailIntent)
}
fun bindTo(currentSport : ListItem){
itemView.heading_textview.setText(currentSport.title)
itemView.description_textview.setText(currentSport.info)
Glide.with(context).load(currentSport.imageResource).into(itemView.image_view)
}
}
}
You can restrict activity restarting in your Manifest if you have same layout for Portrait and Landscape mode.
Add this to your activity in the manifest.
<activity android:name=".activity.YourActivity"
android:label="#string/app_name"
android:configChanges="orientation|screenSize"/>
If you don't want to restrict screen orientation changes, then you can use OnSaveInstanceState method to save your older data when orientation changed. Whatever data you save via this method you will receive it in your OnCreate Method in bundle. Here is the helping link. So here as you have ArrayList of your own class type you also need to use Serializable or Parcelable to put your ArrayList in your Bundle.
Except these making ArrayList as public static is always a solution, But its not a good solution in Object Oriented paratime. It can also give you NullPointerException or loss of data, in case of low memory conditions.
It looks like initializeData is called twice since onCreate is called again on orientation change, you could use some boolean to check if data has been already initialized then skip initializing
What you are doing is you are deleting the values that are passed down to the recyclerview but when the orientation changes the recyclerview reloads from activity and the original data from activity is passed down again and nothing changes, so if you want to save the changes in recyclerview you have to change the original data in the activity so that if the view reloads the data is the same.
I think u initialize adapter in oncreate method in which the whole adapter will be recreated and all datas is also newly created when configuration changes. Because u init data in oncreate method. Try something globally maintain the list and also delete the item in the list in activity when u delete in adapter also. Or try something like view model architecture
Use MVVM pattern in the project. It will manage the orientation state.
MVVM RecyclerView example:
https://medium.com/#Varnit/android-data-binding-with-recycler-views-and-mvvm-a-clean-coding-approach-c5eaf3cf3d72

Problems with nested recyclerview

I have a nested recyclerview which should look like in the .
I implemented it according to this helpful site.
The problem is, that I sometimes have a user with hundreds of items and in that case, it takes half a minute to open the activity.
I have a room database in the backend with two linked tables with foreign keys (users and items) and I select all users to get a user/item list where the items are a list in the user-table.
class userWithItems: (id: Int, name: String, ... ,List)
and I create the inner recycler view with the List of items in the adapter.
Would it be better to make one List UserItems (userid:Int, username:String, ... itemid:Int, itemList) and group them for the outer rv.
Or is there a possibility to get rid of the nested rv and make the design with just one recyclerview-list?
Or is there another solution to make the nested recyclerview work even if there are many items for a user?
code for the adapters:
// Code in Activity: (oncreate)
val recyclerView = findViewById<RecyclerView>(R.id.rv_users)
val adapter = UserAdapter(this)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
mainViewModel = ViewModelProviders.of(this, PassIntViewModelFactory(this.application, online_id)).get(MainViewModel::class.java!!)
mainViewModel.userList.observe(this, Observer {
it?.let {
adapter.setUserList(it)
}
})
data class UsersWithItems(
val id:Int, val username: String, val address, // fields from user table
val items: List<Items> // list of items for current user
)
data class Items (
val id: Int, val itemtext: String, val itemlocation: String, val image: String // ...
)
// UserAdapter (outside)
class UserAdapter internal constructor(
context: Context
) : RecyclerView.Adapter() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var userList = emptyList<UsersWithItems>()
inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val username: TextView = itemView.findViewById(R.id.user_name)
val num_pos: TextView = itemView.findViewById(R.id.user_num_pos)
val address: TextView = itemView.findViewById(R.id.user_addr)
val rv:RecyclerView = itemView.findViewById(R.id.rv_user_items)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val myItemView = inflater.inflate(R.layout.rv_row_user, parent, false)
return MyViewHolder(myItemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val current = userList[position]
holder.username.text="${current.user?.username}"
holder.num_pos.text="${current.items?.size}"
holder.address.text = "${current.user?.address}"
val adapter = UserItemAdapter(holder.rv.context)
adapter.setItems(current.items!!)
holder.rv.adapter = adapter
holder.rv.layoutManager = LinearLayoutManager(holder.rv.context,LinearLayout.VERTICAL,false)
}
internal fun setUserList(userList: List<UsersWithItems>){
this.userList=userList
notifyDataSetChanged()
}
override fun getItemCount() = userList.size
}
class UserItemAdapter internal constructor(
context: Context
) : RecyclerView.Adapter() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var itemList = emptyList<Items>()
inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val itemtext: TextView = itemView.findViewById(R.id.item_text)
val itemlocation:TextView = itemView.findViewById(R.id.item_location)
val image: ImageView = itemView.findViewById(R.id.item_image)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val myItemView = inflater.inflate(R.layout.rv_row_user_items, parent, false)
return MyViewHolder(myItemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val current = itemList[position]
holder.itemtext.text="${current.itemtext}"
holder.itemlocation.text = current.itemlocation
if (current.image.length>0) {
val image = Base64.decode(current.image, 0)
val bitmap = BitmapFactory.decodeByteArray(image, 0, image.size)
holder.image.setImageBitmap(bitmap)
}
}
internal fun setItems(items: List<Items>){
this.itemList=items
notifyDataSetChanged()
}
override fun getItemCount() = itemList.size
}
E. Reuter i have been through this situation the thing is the approach is quite correct by using nested Recycler View. Your code seems to be good. but the queries which you are using to query database. I think you should use queries in Background or on the other threas and show result as you get them instead of querying it from OnCreate or from main thread. Because getting this many items in one go can possibly create lag to activity and decreasing performance. try this out if you have not yet and let me know. What happens. Thanks...
I am editing my answer. the other thing you could do is if you have more than certain amount of items then instead of getting them at the first you should use some thing like pagination to load certain amount of items at once to avoid this lag.
Here i am attaching the code to query certain amount of data per load....
SApp.database!!.resultDao().loadAllUsersByPage(5, 10)
#Query("SELECT * FROM Result LIMIT :limit OFFSET :offset")
fun loadAllUsersByPage(limit: Int, offset: Int): List<Result>
Thank you very much for your answer. I think that paging is really a good approach. But I cannot add the pageing directly since I am getting my data from a roomdatabase like this:
#Query(SELECT * FROM users)
fun getData(): LiveData<List<userWithItems>>
and the actual items are added by room because of a relation between user and items I will have to change this behavior.
I will try something like
#Query(SELECT * FROM users)
fun getUserData(): LiveData<List<Users>>
and then try to add an LiveData observer in the outer recyclerview to get the items in a separate query which uses paging.
I solved the problem. When I thought about pagination it came into my mind that the problem could be that the inner recyclerview has a height of wrap_content and so it needs to build all of the items and makes the rv useless. When I make the height of the inner rv 250dp, it works quite even with 2000 items.
So now I just have to figure out a way to always find the optimal height for the inner rv and solve the scrolling problem but the original problem is solved.
Special thanks to Aman B!

Debugger doesn't stop at breakpoint in Recyclerview adapter

class testAdapter(
private val list: ArrayList<Objects>
) : RecyclerView.Adapter<testViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): testViewHolder {
*BREAKPOINT HERE*
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.X, parent, false)
val viewHolder = testViewHolder(view)
return viewHolder
}
override fun onBindViewHolder(holder: testViewHolder, position: Int) {
holder.bind()
}
override fun getItemCount(): Int { return contentList.size }
}
I can see that program was here, because breakpoint is check-marked. I'm pretty sure, that it's caused by async (by retrofit) call, which gathers item list, but how can I fight with this? I can write code, if I can't debug none of Adapters functions.
I set up adapter like this:
onCreate() {
recyclerView =rootView.findViewById(R.id.test)
gridLayoutManager = /* custom grid layout manager */ (this is fine)
recyclerView.layoutManager = gridLayoutManager
}
asyncFunctionResult(list: List) {
contentAdapter = testAdapter()
recyclerView.adapter = contentAdapter
}
Sorry for pseudo code, but this should be enough. Obviously, there are alot of things wrong in adapter ect., but it should atleast get a hit [stop] from debugger. Any ideas?
For anybody having this problem: try checking out if you forgot to add the recyclerview.layoutManager.
It looks like your list is not filled, onCreateViewHolder is only called when it needs to inflate an item which is when a listitem needs to be shown.
You could try to add an item to contentlist or list (as your code shows 2 lists, I assume this is also pseudocode).

Categories

Resources