I am getting data from an api and showing it on a recyclerview. I have checked many other queries of the same issue but i cant seem to find a way to solve this.
Below is my activity class
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val retrofit = RetrofitClient.instance
jsonApi = retrofit.create(MyApi::class.java)
fetchRecyclerData()
}
private fun fetchRecyclerData() {
compositeDisposable?.add(jsonApi.posts
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe{posts->displayData(posts)})
}
private fun displayData(posts: List<Post>?) {
val adapter = PostAdapter(this,posts!!)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(true)
adapter.notifyDataSetChanged()
}
}
Below is my adapter
class PostAdapter(internal var context: Context, internal var
postList:List<Post>):RecyclerView.Adapter<PostViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.post_layout,parent,false)
return PostViewHolder(itemView)
}
override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
holder.tvAuthor.text = postList[position].userId.toString()
holder.tvContent.text = StringBuilder(postList[position].body.substring(0,20))
.append("...").toString()
holder.tvTitle.text = postList[position].title
}
override fun getItemCount(): Int {
return postList.size
}
}
Logcat
06-17 13:29:06.047 23952-23952/com.malikali.kotlinxrxjava2xretrofit2 W/RecyclerView: No adapter attached; skipping layout
06-17 13:29:06.057 23952-23952/com.malikali.kotlinxrxjava2xretrofit2 W/RecyclerView: No adapter attached; skipping layout
Below is my xml file. I still don't understand what am doing wrong in this case.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
You should always call notifyDataSetChanged() on adapter after submitting data so change your code like this:
private fun displayData(posts: List<Post>?) {
val adapter = PostAdapter(this,posts!!)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(true)
adapter.notifyDataSetChanged()
}
Please also share your xml. three possible conditions
1: the recycler view must be of 0 height or width
2: 'posts' list is empty
3: make logs in the subscribe to see if not throwing any exception else implement error of rxjava to check for possible exceptions
Related
This question already has an answer here:
No Adapter Attached Skipping layout error with RecyclerView Adapter
(1 answer)
Closed 28 days ago.
I want to populate a RecyclerView with data from Firebase. I tried something, I built an Adapter and tried to populate the RecyclerView but not showing any data in the RecyclerView and I get no errors.
In firebase console shows like the query is working but in the simulator not show anything and i dont ger errors.
[![The Recyclerview from the simulator][1]][1]
My layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".CamionesActivos">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvCamioness"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="10dp"
android:layout_marginTop="7dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="7dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="#layout/tarjeta_recycler" />
</androidx.constraintlayout.widget.ConstraintLay
my adapter:
class AdaptadorCamiones(private val camionlist:ArrayList<datos>)
:RecyclerView.Adapter<AdaptadorCamiones.MyViewHolder>() {
class MyViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
val placa :TextView?=itemView.findViewById(R.id.tvPlacaDato)
val peso: TextView?=itemView.findViewById(R.id.tvPesoDato)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView=
LayoutInflater.from(parent.context).inflate(R.layout.activity_camiones_activos,parent,false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.placa?.text ?: camionlist[position].Placa
holder.peso?.text ?: camionlist[position].Peso
}
override fun getItemCount(): Int {
return camionlist.size
}
}
my activity:
class CamionesActivos : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var camionList: ArrayList<datos>
private var db=Firebase.firestore
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camiones_activos)
recyclerView=findViewById(R.id.rvCamioness)
recyclerView.layoutManager=LinearLayoutManager(this, LinearLayoutManager.VERTICAL ,false)
camionList= arrayListOf()
recyclerView.adapter = AdaptadorCamiones(camionList)
db=FirebaseFirestore.getInstance()
db.collection("Camiones")
.get()
.addOnSuccessListener {
if(!it.isEmpty){
for (data in it.documents){
val camion: datos? =data.toObject(datos::class.java)
if (camion !=null){
camionList.add(camion)
}
}
}
}
.addOnFailureListener{
Toast.makeText(this, it.toString(), Toast.LENGTH_SHORT).show()
}
}
}```
[1]: https://i.stack.imgur.com/6xmfX.png
The reason why data isn't displaying is because the RecyclerView's adapter (AdaptadorCamiones) isn't aware that it's dataset (camionList) has been updated. Whenever you insert data into the collection that backs the RecyclerView's adapter you need to inform it that a change by calling one of it's many notify...(...) methods. The quick and dirty solution would be to call notifyDataSetChanged() and it will trigger the RecyclerView to be redrawn and you're new data will be at the bottom of the list.
The proper solution would be to use the of the adapters notifyItemInserted(int) overload. This method will inform the adapter that a single item was inserted at the given position. The adapter will trigger the RecyclerView to efficiently update and animate the new item into the specified position.
Calling notifyDataSetChanged() will force the RecyclerView's layout manager to rebind all view and relayout all visible items. Unless you're using stable IDs the RecycleView will not animate any structural changes using this method. Using the more specific notify... overloads will always be more efficient. There are overloads for notify the adapter of several items being inserted, as well as the reciprocal methods for removing one or many items. There are also overloads to notify the adapter of that items have been updated or swapped positions. By calling this overloads instead of notifyDataSetChanged() the RecyclerView will be able to animate the structural changes.
#cincy_anddeveloper
i changed the main code using notifyItemInserted(position) but noting happens and the console shows a error
main code:
class CamionesActivos : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var camionList: ArrayList<datos>
private lateinit var adapter: AdaptadorCamiones
private lateinit var linearLayoutManager: LinearLayoutManager
private var db=Firebase.firestore
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camiones_activos)
recyclerView=findViewById(R.id.rvCamioness)
linearLayoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = linearLayoutManager
//recyclerView.layoutManager=LinearLayoutManager(this,
LinearLayoutManager.VERTICAL ,false)
camionList= arrayListOf()
adapter= AdaptadorCamiones(camionList)
recyclerView.adapter=adapter
db=FirebaseFirestore.getInstance()
db.collection("Camiones")
.get()
.addOnSuccessListener {
if(!it.isEmpty){
for (data in it.documents){
val camion: datos? =data.toObject(datos::class.java)
if (camion !=null){
camionList.add(camion)
adapter.notifyItemInserted(camionList.size+1)
}
}
}
}
.addOnFailureListener{
Toast.makeText(this, it.toString(), Toast.LENGTH_SHORT).show()
}
}
}
Console error:
E/RecyclerView: No adapter attached; skipping layout
I have a situation where I have 3 RecyclerView on a single Layout. They are dependent on each other in a certain way. Data is coming from Room Database.
Question Prototype (Problem statement): Let's say you have floors like (Floor1, Floor2, Floor3 etc.) and inside each floor you have rooms like (Room1, Room2, Room3 etc.) and inside each room you have People with name like (PersonA, PersonB, PersonC).
Given constraint is that : A person cannot be in two different rooms at same type.
Edit 1: Floors, Rooms and Persons are coming from the database in the form of a list of strings.
How would you show that using maybe ( a recycler view ) or anything on a single screen layout.
There can be infinite number of floors, rooms and persons. But that information is fetch from a room database.
My approach : (This is not a complete approach), But I am thinking like having one RecyclerView at the top which holds number of floors. We use a query to get the total floors from the database and sort them and display. During the onBindViewHolder() in the adapter for floors we have a condition which checks if there is a room associated with that floor and if there is then we make another query to fetch from database and show it (maybe using a new recycler View) but I don't know how to do that. Is nested recycler view a thing that can be used here.
It doesn't stop here as we make another query to database using that room name to find all the persons inside that room. Which is another recycler view.
I am thinking of applying this approach but I feel there are many stoppage for implementing this. And I am not sure if this is the way to handle such things.
Image for reference :
I am looking for any information if you have been through any such situations then what approach did you followed. Is there any library that can be used to simplify this task or anything that you can provide knowledge of would be helpful. Thanks!
Edit 2: What's working now :
I tried implementing the nested recycler view. Things seems to be working fine as now I have two adapters currently (for floor and rooms) and I am inflating the floor adapter from the fragment and inflating the rooms adapter from onBindViewHolder of FloorsAdapter.
The problem I am facing now that inside recycler view I am getting the list of floors correctly but for the list of rooms(in child recycler view) I am only getting it for last floor.
Check this image for reference (Floor 2 and 1 also have rooms but I am only getting room C1 which is present in last floor which is 3) :
Current code in Adapter :
class FloorsAdapter(
private val controlPanelViewModel: ControlPanelViewModel,
private val activity: FragmentActivity?
) : RecyclerView.Adapter<FloorsAdapter.FloorViewHolder>() {
private var floorList = emptyList<String>()
private lateinit var roomAdapter: RoomAdapter
inner class FloorViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): FloorViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemControlPanelFloorsBinding.inflate(layoutInflater, parent, false)
return FloorViewHolder(binding.root)
}
override fun onBindViewHolder(holder: FloorsAdapter.FloorViewHolder, position: Int) {
val item = floorList[position]
ListItemControlPanelFloorsBinding.bind(holder.itemView).apply {
Timber.d("Current floor is $item, Floor List is : $floorList")
tvFloor.text = item
roomAdapter = RoomAdapter(controlPanelViewModel, activity)
rvRoomControlPanel.adapter = roomAdapter
rvRoomControlPanel.layoutManager = LinearLayoutManager(activity)
controlPanelViewModel.getAllRooms(item).observeForever(Observer {
Timber.d("List of rooms : $it")
//Finding distinct rooms
val distinct = it.toSet().toList()
Timber.d("Distinct rooms list : $distinct")
roomAdapter.roomList(distinct)
})
}
}
#SuppressLint("NotifyDataSetChanged")
fun floorList(floors: List<String>) {
this.floorList = floors
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return floorList.size
}
}
class RoomAdapter(
private val controlPanelViewModel: ControlPanelViewModel,
private val activity: FragmentActivity?
) : RecyclerView.Adapter<RoomAdapter.RoomViewHolder>() {
private var roomList = emptyList<String>()
inner class RoomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RoomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemControlPanelRoomsBinding.inflate(layoutInflater, parent, false)
return RoomViewHolder(binding.root)
}
override fun onBindViewHolder(holder: RoomViewHolder, position: Int) {
val item = roomList[position]
ListItemControlPanelRoomsBinding.bind(holder.itemView).apply {
Timber.d("Current room is $item")
tvRoom.text = item
}
}
#SuppressLint("NotifyDataSetChanged")
fun roomList(room: List<String>) {
this.roomList = room
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return roomList.size
}
}
Code in Fragment for inflating the Floors Adapter:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.rvFloors.adapter = adapter
binding.rvFloors.layoutManager = LinearLayoutManager(requireContext())
controlPanelViewModel.getAllFloors.observe(viewLifecycleOwner, Observer{
Timber.d("List is $it")
//Remove duplicates from received list
val distinct = it.toSet().toList()
Timber.d("List after removing duplicates: $distinct")
adapter.floorList(distinct)
})
Timber.d("Adapter: $adapter" )
}
Edit 3: Going through the logs I found something which explains why is it happening. The list of floors is getting executed first because of this
controlPanelViewModel.getAllFloors.observe(viewLifecycleOwner, Observer{
Timber.d("List is $it")
//Remove duplicates from received list
val distinct = it.toSet().toList()
Timber.d("List after removing duplicates: $distinct")
adapter.floorList(distinct)
})
As we are inflating the rooms recycler view from floors adapter here
roomAdapter = RoomAdapter(controlPanelViewModel, activity)
rvRoomControlPanel.adapter = roomAdapter
rvRoomControlPanel.layoutManager = LinearLayoutManager(activity)
controlPanelViewModel.getAllRooms(item).observeForever(Observer {
Timber.d("List of rooms : $it")
//Finding distinct rooms
val distinct = it.toSet().toList()
Timber.d("Distinct rooms list : $distinct")
roomAdapter.roomList(distinct)
})
It is going to be little delayed as data is being fetched from Room. So instead of doing something like (Floor 1 -> Room1, Room2 we are getting something like (Floor1, Floor2 -> Room3, Room4). The data about rooms for previous floors is getting lost.
What to do here? How to stop the execution of next floors unless we have fetched all the rooms and shown using the textview.
I have tried replicate the exact scenario with one vertical recycler view and horizontal recycler view. Here is the thing, one vertical recyclerview for Floors. Then one in floor item, list of room item views can be added to vertical linear layout container. A single room item view contains horizontal recyclerview for person.
Floor Vertical RV -> Adding Custom Room Items to Vertical LL -> Person
Horizontal RV
Hope it helps.
Activity:
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.mainRv)
recyclerView.apply {
adapter = FloorAdapter(getFloors(), this#MainActivity)
layoutManager = LinearLayoutManager(this#MainActivity)
setHasFixedSize(true)
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
}
}
fun getFloors(): List<Floor> {
val list = ArrayList<Floor>()
for (i in 0 until 6) {
list.add(Floor())
}
return list
}
}
<androidx.constraintlayout.widget.ConstraintLayout 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/mainRv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Adapters:
class FloorAdapter(val floors: List<Floor>, val context:Context) : RecyclerView.Adapter<FloorViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FloorViewHolder {
return FloorViewHolder(LayoutInflater.from(context).inflate(R.layout.floor_item,null))
}
override fun onBindViewHolder(holder: FloorViewHolder, position: Int) {
holder.bindView(floors[position])
}
override fun getItemCount(): Int {
return floors.size
}
}
class PersonAdapter(val persons: List<Person>, val context:Context) : RecyclerView.Adapter<PersonViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PersonViewHolder {
return PersonViewHolder(LayoutInflater.from(context).inflate(R.layout.person_item,null))
}
override fun onBindViewHolder(holder: PersonViewHolder, position: Int) {
holder.bindView(persons[position])
}
override fun getItemCount(): Int {
return persons.size
}
}
View Holders:
class PersonViewHolder(val view:View) : RecyclerView.ViewHolder(view) {
fun bindView(person: Person) {
view.findViewById<TextView>(R.id.person_tv).text = "Person:${person.personId}"
}
}
class FloorViewHolder(val view:View) : RecyclerView.ViewHolder(view){
fun bindView(floor: Floor){
val str = "Floor : ${floor.floorId}"
view.findViewById<TextView>(R.id.floor).text = str
view.findViewById<LinearLayout>(R.id.room_container).let { ll->
ll.removeAllViews()
floor.rooms.forEach{
val myView= LayoutInflater.from(view.context).inflate(R.layout.room_item,null)
myView.findViewById<TextView>(R.id.room).text = "RoomId :${it.roomId}"
myView.findViewById<RecyclerView>(R.id.persons_rv)?.apply {
adapter = PersonAdapter(it.persons,view.context)
layoutManager = LinearLayoutManager(view.context,LinearLayoutManager.HORIZONTAL,false)
setHasFixedSize(true)
}
ll.addView(myView)
}
}
}
}
Data Class:
data class Floor(val floorId: Int = (0..900).random()) {
var rooms = arrayListOf<Room>()
init {
for (i in 0 until 50) {
rooms.add(Room())
}
}
}
data class Room(val roomId: Int = (0..900).random()) {
var persons = arrayListOf<Person>()
init {
for (i in 0 until 200) {
persons.add(Person())
}
}
}
data class Person(val personId: Int = (0..900).random())
floor_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/floor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="#android:color/holo_red_dark"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="#+id/room_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/floor"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
person_item.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:padding="18dp"
android:id="#+id/person_tv"
android:textSize="18sp"
android:layout_margin="10dp"
android:background="#android:color/holo_blue_light"
android:textColor="#color/black"
android:layout_height="wrap_content">
</com.google.android.material.textview.MaterialTextView>
room_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:padding="20dp"
android:layout_height="match_parent">
<TextView
android:id="#+id/room"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#android:color/holo_green_dark"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/persons_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="#id/room" />
</androidx.constraintlayout.widget.ConstraintLayout>
I have recyerlView. I loaded data from the server. When it initially loads it height is normal, but when I go to the next activity through item click. And come back it slowly increasing the height of the child. I tried to debug this and found that onResume api call causing the issue. But What I am doing wrong in layout I don't get it.
FirstLayout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/reyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
FirstActivity.kt
class FirstActivity : BaseActivity() {
lateinit var binding: FirstLayoutActivityLayoutBinding
private val viewModel: FirstViewModel by inject()
private var listAdapter: ListAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupViewModel()
binding = FirstLayoutActivityLayoutBinding.inflate(layoutInflater)
setContentView(binding.root)
}
private fun setupViewModel() {
viewModel.livedata.observe(this, { list ->
setupAdapter(list)
})
}
private fun setupAdapter(list: List<String>) {
initializeAdapter(list)
listAdapter?.updateItemsList(list)
binding.recyclerView.apply {
addItemDecoration(HeaderItemDecoration(context))
val itemDecorator = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
itemDecorator.setDrawable(ContextCompat.getDrawable(context, R.drawable.divider)!!)
addItemDecoration(itemDecorator)
adapter = listAdapter
}
}
private fun initializeAdapter(list: List<String>) {
listAdapter = ListAdapter(list.toMutableList())
}
override fun onResume() {
super.onResume()
viewModel.fetchItem() // noraml retrofit call
}
}
HeaderItemDecoration is used from this answer.
ListAdapter.kt
class ListAdapter(private val list: MutableList<String>) : RecyclerView.Adapter<Adapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
ListLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bindItem(list[position])
}
override fun getItemCount(): Int {
return list.size
}
inner class MyViewHolder(private val binding: ListLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
fun bindItem(s: String) {
binding.cool.text = s
}
}
}
ListLayout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/root"
android:background="#color/red"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/cool"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
I am adding my view to my Youtube Link. Please look at what the issue is. Thanks
I didn't add any logic to increase the height
looks like a problem with multiple ItemDecorations, which are set every time new data is fetched. to be exact: multiple DividerItemDecorations, which adds small padding to the items (for dividing them). note methods name starts with add..., so every time you are adding new, old stays there and every instance of this divider is adding some padding. if you would implement e.g. pull-to-refresh or auto-refresh in background every e.g. 10 secs then every refresh GUI method call (setupAdapter) would add some space without leaving Activity. currently you are fetching data only once, in onResume, so every move-activity-to-foreground action will add one divider (when data will be fetched properly)
move this part of code to onCreate for setting dividers only once, thats proper place for some additional styling by code
binding.recyclerView.apply {
addItemDecoration(HeaderItemDecoration(context))
val itemDecorator = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
itemDecorator.setDrawable(ContextCompat.getDrawable(context, R.drawable.divider)!!)
addItemDecoration(itemDecorator)
}
and inside your setupAdapter method set only adapter to RecyclerView, don't style it (multiple times)
private fun setupAdapter(list: List<String>) {
initializeAdapter(list)
listAdapter?.updateItemsList(list)
binding.recyclerView.adapter = listAdapter
}
Hey I have Reyclerview with DiffUtill using ListAdapter. I added element through submitList function. But when updating the list view is not redrawing the element. Until I used notifyDataSetChanged() or setting adapter again. So what the use case of DiffUtill?. What is the proper way of doing to redraw item when item is updated in list as well as in reyclerview.
MainActivity
class MainActivity : AppCompatActivity() {
private var list = mutableListOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
private lateinit var binding: ActivityMainBinding
var i = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
Log.e("List", " $list")
val intAdapter = IntAdapter()
binding.recylerview.adapter = intAdapter
intAdapter.submitList(list)
binding.button.setOnClickListener {
list.add(++i)
intAdapter.submitList(list)
// binding.recylerview.adapter = intAdapter
// intAdapter.notifyDataSetChanged()
}
}
}
IntAdapter
class IntAdapter : ListAdapter<Int, IntViewHolder>(comparator) {
companion object {
private val comparator = object : DiffUtil.ItemCallback<Int>() {
override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
return oldItem == newItem
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IntViewHolder {
return IntViewHolder(
IntLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: IntViewHolder, position: Int) {
holder.bindItem(getItem(position))
}
}
IntViewHolder
class IntViewHolder(val binding: IntLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
fun bindItem(item: Int?) {
binding.intNumber.text = item.toString()
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/recylerview"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
int_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/intNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
You need to ensure you pass a different instance of List each time you call submitList. If you pass a List, mutate it, and then pass that same List instance again, DiffUtil is only comparing the same list to itself, so it will assume nothing has changed in the list and won't update anything. It doesn't have some sort of memory of what the List contained back when you first submitted it.
To generalize further, you must not use a mutable List with ListAdapter at all. ListAdapter assumes the list you pass to submitList does not change. If you mutate it, there can be unexpected bugs.
Two ways to resolve this in your code.
Create a read-only copy of the list each time you pass it:
intAdapter.submitList(list.toList())
Don't use a MutableList at all. Create a new List every time you modify what should be in the List. This is the simpler, less error-prone solution.
class MainActivity : AppCompatActivity() {
private var list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
private lateinit var binding: ActivityMainBinding
var i = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
Log.e("List", " $list")
val intAdapter = IntAdapter()
binding.recylerview.adapter = intAdapter
intAdapter.submitList(list)
binding.button.setOnClickListener {
list += ++i // create a new list from old list contents plus a new item
intAdapter.submitList(list)
}
}
}
Side note: when you have var combined with Mutable____ that should be kind of a red flag. It should be rare that you need two different ways to change something. That is error-prone.
You need to submit a new List, currently you are submitting that same List which has been mutated.
Inside your click listener try something like :
binding.button.setOnClickListener {
val current = intAdapter.currentList
val update = current + (current.size + 1) // returns a new list with added value
intAdapter.submitList(update)
}
Example with this logic :
MainActivity is as follows.
Nothing comes out on the screen, but I don't know which part is wrong.
How can I add the value of TV_item_name?
And in the log, it's from the DetailViewAdapter of the inner class.
Log.d (logTag,onCreateViewHolder11iscaled") value is also not output, so what is the problem?
package com.example.test_recyclerview
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.test_recyclerview.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding : ActivityMainBinding
var logTag : String? = "로그 MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
Log.d(logTag,"111 onCreate is called")
binding.mRecyclerView.layoutManager = LinearLayoutManager(this)
val adapter = DetailViewAdapter()
Log.d(logTag,"222 onCreate is called")
binding.mRecyclerView.adapter = adapter
}
override fun onDestroy() {
super.onDestroy()
binding.mRecyclerView.adapter = null
}
inner class DetailViewAdapter : RecyclerView.Adapter<DetailViewAdapter.ViewHolder>() {
private var list = ArrayList<String>()
var logTag : String? = "로그 MainActivity"
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailViewAdapter.ViewHolder {
Log.d(logTag,"onCreateViewHolder11 is called")
list = getItemList()
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_custom_row, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Log.d(logTag,"onBindViewHolder is called")
for (i in 1 until list.size) {
Log.d(logTag,"onBindViewHolder is called // list[$i] =" + list[i])
holder.tvItem.text = list[i]
}
}
override fun getItemCount(): Int {
return list.size
}
private fun getItemList(): ArrayList<String> {
for (i in 1..8) {
list.add(i, "Item $i")
}
return list
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvItem : TextView = view.findViewById(R.id.tv_item_name)
val cardViewItem : CardView = view.findViewById(R.id.card_view_item)
}
}
}
item_custom_row.xml
<?xml version="1.0" encoding="utf-8"?>
`enter code here`<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="wrap_content">
<androidx.cardview.widget.CardView
android:id="#+id/card_view_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:padding="10dp"
app:cardCornerRadius="5dp"
app:cardElevation="3dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:contentDescription="#string/app_name"
android:src="#mipmap/ic_launcher" />
<TextView
android:id="#+id/tv_item_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textColor="#android:color/black"
android:textSize="18sp"
android:textStyle="bold"
tools:text="Item" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/m_RecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
The issue is you're assigning list in onCreateViewHolder but it won't be called since your list.size is returning 0.
Rightly pointed out in this answer
Create list outside adapter & pass it from constructor
Change implementation in onBindViewHolder
Following is the complete example for your use case:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
var logTag: String? = "로그 MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
Log.d(logTag, "111 onCreate is called")
binding.mRecyclerView.layoutManager = LinearLayoutManager(this)
val adapter = DetailViewAdapter(getItemList())
Log.d(logTag, "222 onCreate is called")
binding.mRecyclerView.adapter = adapter
}
private fun getItemList(): ArrayList<String> {
val list = ArrayList<String>()
for (i in 1..8) {
list.add("Item $i")
}
return list
}
override fun onDestroy() {
super.onDestroy()
binding.mRecyclerView.adapter = null
}
inner class DetailViewAdapter(private val list: ArrayList<String>) :
RecyclerView.Adapter<DetailViewAdapter.ViewHolder>() {
var logTag: String? = "로그 MainActivity"
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): DetailViewAdapter.ViewHolder {
Log.d(logTag, "onCreateViewHolder11 is called")
val view =
LayoutInflater.from(parent.context).inflate(R.layout.item_custom_row, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Log.d(logTag, "onBindViewHolder is called")
holder.tvItem.text = list[position]
}
override fun getItemCount(): Int {
return list.size
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvItem: TextView = view.findViewById(R.id.tv_item_name)
val cardViewItem: CardView = view.findViewById(R.id.card_view_item)
}
}
}
From the code you posted I can see two problems with your code.
First one is that you are trying to populate the list of data from inside the view holder. What happens is the following:
The adapter is created
The adapter wants to know how many viewholders it should create to see how to populate the recycler view.
The adapter calls getItemCount and this method returns 0, since the list hasen't been populated.
Nothing else is called since nothing else has to be executed.
So, to fix this, the easiest way would be to make getItemCount return 8 and you are set. But, a better way to fix this is to instaintiate your list outside of your adapter, in your activity for example, and pass it as a constructor parameter when you initialize your adapter.
The second problem I'm seeing is on the method onBindViewHolder. You are iterating trough the list to set the text and this will cause that for all items you will only set the text as in the last item (item 8). You need to remember that onBindViewHolder is a method that is called when a view holder needs to refresh it contents because it is going to be used to display a different item of the list, that's why this method is passed the position as parameter, so you can do something like:
holder.tvItem.text = list[position]
** As a side note for the second issue, a more general approach I have seen and used to render the contents of the view holder, is to create a public method called "bind" which is passed the item that needs to be rendered and on the view holder you will have the logic on how to paint it. Something like:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(list[position])
}
/// Inside the view holder
fun bind(item: String) {
tvItem.text = item
}