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
Related
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
}
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
I'm new at kotlin and I would like some help in how to set focus of the scroll on the last element of an ArrayList<tags> here a look of my code
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/RVTags"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="12dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="#id/startStop"
app:layout_constraintTop_toBottomOf="#id/ui_power_range_inventory_layout"
/>
class FindTagsAdapter internal constructor(
private val ServicesList: ArrayList<Tag>
) :
RecyclerView.Adapter<FindTagViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FindTagViewHolder {
return FindTagViewHolder(LayoutInflater.from(parent.context), parent)
}
override fun onBindViewHolder(holder: FindTagViewHolder, position: Int) {
holder.bind(ServicesList[position])
}
override fun getItemCount(): Int {
return ServicesList.size
}
}
You can scroll to the last item of your list with the smoothScrollToPosition() function.
You can create a function in your class for that action.
In my code, I assume that you use two global variables for storing your RecyclerView reference and your list of items and that you have already initialized your RecyclerView.
private var recyclerView: RecyclerView? = null
private var servicesList = arrayListOf<Tag>()
...
private fun focusLastItem()
{
if (servicesList.isNotEmpty()) {
recyclerView?.smoothScrollToPosition(serviceList.size - 1)
}
}
Then you can just call the focusLastItem function every time your list is updated.
Solve my problem:
RecyclerView.scrollToPosition(var.lastIndex)
That way it scroll automatically to bottom.
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.