getting the error : recyclerview No adapter attached; skipping layout - android

Here is the logcat messagesThis is main activity and i'm getting error at var userData = snaps.getValue(Users::class.java) and No adapter attached; skipping layout
firebaseAuth = FirebaseAuth.getInstance()
userRecyclerView = findViewById(R.id.recyclerView)
userRecyclerView.layoutManager = LinearLayoutManager(this)
userRecyclerView.hasFixedSize()
userList = arrayListOf<Users>()
getUserList()
private fun getUserList() {
dbref = FirebaseDatabase.getInstance().getReference("User")
dbref.addValueEventListener(object :ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
userList.clear()
if(snapshot.exists()){
for (snaps in snapshot.children){
var userData = snaps.getValue(Users::class.java)
userList.add(userData!!)
}
val mAdapter = adapter(userList)
userRecyclerView.adapter = mAdapter
}
}
override fun onCancelled(error: DatabaseError) {
}
})
}
This is data class:
class Users (
var userId :String? =null,
var password :String? =null
)
This is the adapter class:
class adapter(private val userList:ArrayList<Users>)
:RecyclerView.Adapter<adapter.ViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView =LayoutInflater.from(parent.context).inflate(R.layout.users,parent,false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val current = userList[position]
holder.userID.text =current.userId
}
override fun getItemCount(): Int {
return userList.size
}
class ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val userID :TextView= itemView.findViewById(R.id.userTv)
}
}
help me :(..................................
.......................................
............................................

This might not be the cause of your problem, but you don't need to assign adapter in getUserList(). Instead, you can do it right where you assign a LayoutManager. But to answer your question, I need more details about your error, like log messages.

this is the problem :
var userData = snaps.getValue(Users::class.java)
userList.add(userData!!)
you're adding a string to a list of type Users.

No adapter attached; skipping layout
The adapter is the way in which we get the list of items into the recycler view. If there is no adapter then there are no items and there is nothing to layout.
It's been a while since I used a recycler view, but I seem to remember this was a warning in logcat simply telling you that its jumped over the layout routine of the recycler view because there are no items to layout.
If you attach the adapter and call notifyDataSetChanged, then you will force the recycler view to run the layout routine, now that there are items to display.
override fun onDataChange(snapshot: DataSnapshot) {
userList.clear()
if(snapshot.exists()){
for (snaps in snapshot.children){
var userData = snaps.getValue(Users::class.java)
userList.add(userData!!)
}
val mAdapter = adapter(userList)
userRecyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged()
}
}

Related

Getting 'E/RecyclerView: No adapter attached; skipping layout' in Kotlin tabbedActivity

Hi I am beginning with Kotlin and tried to create tabbedActicity with parsed list. I am getting data with retrofit from google APIs.
Then I try to pass it to recycleView.
I tried many ways, but I keep getting error: E/RecyclerView: No adapter attached; skipping layout
Corresponding textView is present in score_row.xml layout and RecyclerView in thirdtab.xml layout.
thirdTabFragment.kt :
class thirdtabFragment : AppCompatActivity() {
private lateinit var binding: ThirdtabBinding
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
binding = ThirdtabBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
val apiInterface = APIClient.client.create(ApiInterface::class.java)
val call = apiInterface.getScore()
call.enqueue(object : Callback<List<Item>> {
override fun onResponse(call: Call<List<Item>>, response: Response<List<Item>>) {
Log.d("Success!", response.toString())
showContenders(response.body()!!)
}
override fun onFailure(call: Call<List<Item>>, t: Throwable) {
Log.e("Failed Query :(", t.toString())
}
})
}
private fun showContenders(contenderList: List<Item>) {
binding.recyclerView.apply{
layoutManager = LinearLayoutManager(this#thirdtabFragment)
adapter = ScoreAdapter(contenderList)
}
}
ScoreAdapter.kt :
class ScoreAdapter(private val contenderList: List<Item>) : RecyclerView.Adapter<ScoreAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.score_row, parent, false)
return ViewHolder(view)
}
override fun getItemCount() = contenderList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val contender = contenderList[position]
holder.contenderName.text = contender.contenderName
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val contenderName: TextView = itemView.findViewById(R.id.contenderName_tv)
}
}
Attach the adapter sooner, in the onCreate method.
For that make contenderList in ScoreAdapter public
var contenderList = listOf<Item>()
set(value){
field = value
notifyDataSetChanged()
}
then
//move this code to onCreate
adapter = ScoreAdapter()
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter
Then modify the onResponse method
override fun onResponse(call: Call<List<Item>>, response: Response<List<Item>>) {
Log.d("Success!", response.toString())
adapter.contenderList = response.body()!!
}

How to disable the auto scroll of a RecyclerView (ListAdapter) that happens when an item is updated?

BACKGROUND
I have a UI that shows a list of users' fullnames with a like/dislike button for each item. I am using a ListAdapter that under the hood uses DiffUtil and AsyncListDiffer APIs. The list of users is received as a LiveData from a Room database and it's ordered by "isLiked".
PROBLEM
Whenever the like button is tapped, Room as I am using a LiveData will re-submit the new data to the adapter. The problem is that as the list is ordered by "isLiked", the liked user will change its position and the RecyclerView will always sroll to the new position.
I don't want to see the new position of the updated item. So, how can I disable the auto scroll behavior?
WHAT I TRIED
MainActivity.kt
..
val userAdapter = UsersAdapter(this)
val ll = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recyclerView.apply {
layoutManager = ll
adapter = userAdapter
itemAnimator = null
setHasFixedSize(true)
}
viewModel.users.observe(this, {
// This will force the recycler view to scroll back to the previous position
// But it's more of a workaround than a clean solution.
val pos = ll.findFirstVisibleItemPosition()
userAdapter.submitList(it) {
recyclerView.scrollToPosition(pos)
}
})
..
UsersAdapter.kt
class UsersAdapter(
private val clickListener: UserClickListener
) : ListAdapter<UserEntity, UsersAdapter.UserViewHolder>(DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false)
return UserViewHolder(view)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val userEntity = getItem(position)
holder.bind(userEntity, clickListener)
}
class UserViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val textView: TextView = view.findViewById(R.id.fullName)
private val fav: ImageButton = view.findViewById(R.id.fav)
fun bind(user: UserEntity, clickListener: UserClickListener) {
textView.text = user.fullName
val favResId = if (user.favorite) R.drawable.like else R.drawable.dislike
fav.setImageResource(favResId)
fav.setOnClickListener {
val newFav = !user.favorite
val newFavResId = if (newFav) R.drawable.like else R.drawable.dislike
fav.setImageResource(newFavResId)
clickListener.onUserClicked(user, newFav)
}
}
}
interface UserClickListener {
fun onUserClicked(user: UserEntity, isFavorite: Boolean)
}
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<UserEntity>() {
override fun areItemsTheSame(
oldUser: UserEntity,
newUser: UserEntity
) = oldUser.id == newUser.id
override fun areContentsTheSame(
oldUser: UserEntity,
newUser: UserEntity
) = oldUser.fullName == newUser.fullName && oldUser.favorite == newUser.favorite
}
}
}
I tried using a regular RecyclerView adapter and DiffUtil with detect moves set to false.
I added the AsyncListDiffer as well.
I tried the ListAdapter, and even tried the paging library and used the PagedListAdapter.
DiffUtil's callback changes the auto scrolling, but i couldn't get the desired behavior.
Any help is greatly appreciated!

How to preserve scroll position of a RecyclerView after a configuration change when using MVVM viewmodel and livedata?

READ FIRST:
Apologies, it seems I have played myself. I was using RecyclerView in my xml earlier, but switched it over for CardStackView (it still uses the exact same RecyclerView adapter). If I switch back to RecyclerView, the original code below works - the scroll position is saved and restored automatically on configuration change.
I'm using a MVVM viewmodel class which successfully retains list data for a RecyclerView after a configuration change. However, the previous RecyclerView position is not restored. Is this expected behaviour? What would be a good way to solve this?
I saw a blog post on medium briefly mentioning you can preserve scroll position by setting the adapter data before setting said adapter on the RecyclerView.
From what I understand, after a configuration change the livedata that was being observed earlier gets a callback. That callback is where I set my adapter data. But it seems this callback happens after the onCreate() function finishes by which point my RecyclerView adapter is already set.
class MainActivity : AppCompatActivity() {
private val adapter = MovieAdapter()
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Create or retrieve viewmodel and observe data needed for recyclerview
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
viewModel.movies.observe(this, {
adapter.items = it
})
binding.recyclerview.adapter = adapter
// If viewmodel has no data for recyclerview, retrieve it
if (viewModel.movies.value == null) viewModel.retrieveMovies()
}
}
class MovieAdapter :
RecyclerView.Adapter<MovieAdapter.MovieViewHolder>() {
var items: List<Movie> by Delegates.observable(emptyList()) { _, _, _ ->
notifyDataSetChanged()
}
class MovieViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val binding = ItemMovieCardBinding.bind(itemView)
fun bind(item: Movie) {
with(binding) {
imagePoster.load(item.posterUrl)
textRating.text = item.rating.toString()
textDate.text = item.date
textOverview.text = item.overview
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_movie_card, parent, false)
return MovieViewHolder(view)
}
override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount() = items.size
}
class MainViewModel : ViewModel() {
private val _movies = MutableLiveData<List<Movie>>()
val movies: LiveData<List<Movie>> get() = _movies
fun retrieveMovies() {
viewModelScope.launch {
val client = ApiClient.create()
val result: Movies = withContext(Dispatchers.IO) { client.getPopularMovies() }
_movies.value = result.movies
}
}
}
Set adapter only after its items are available.
viewModel.movies.observe(this, {
adapter.items = it
binding.recyclerview.adapter = adapter
})

Array list in kotlin not storing data after initialization

I am trying to fetch data from the firebase realtime database and I am storing it in a list of User data class to display it in the recycler view. But the list is not storing any data. I tried to add some dummy data during initialization and that is working perfectly. But after initialization, it is not storing anything.
I tried with the mutable list too, but that is also not working.
The main fragment where I am retrieving the data:
class UserChatFragment : Fragment() {
lateinit var mobileno: String
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_user_chat, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val session = sessionManager(context!!)
mobileno = session.getLoginContact()
val UserList = getusersdata()
Chat_recyclerview.adapter = UsersAdapter(UserList)
Chat_recyclerview.layoutManager = LinearLayoutManager(context)
contact.text = mobileno
}
private fun getusersdata(): List<User> {
val list: = ArrayList<User>()
val databaseReference =
FirebaseDatabase.getInstance().reference.child("Users").child("Connections")
.child(mobileno)
databaseReference.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
for (data: DataSnapshot in snapshot.children) {
val userReference = FirebaseDatabase.getInstance().reference.child("Users")
.child("Accounts").child(data.key!!)
userReference.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
val item = User(
snapshot.child("name").value.toString(),
snapshot.child("profilePicture").value.toString(),
snapshot.child("phone").value.toString()
)
list.add(item)
}
}
override fun onCancelled(error: DatabaseError) {
Toast.makeText(context, error.message, Toast.LENGTH_SHORT).show()
}
})
}
}
}
override fun onCancelled(error: DatabaseError) {
Toast.makeText(context, error.message, Toast.LENGTH_SHORT).show()
}
})
return list
}
}
Adapter class:
class UsersAdapter(private val userList: List<User>) :
RecyclerView.Adapter<UsersAdapter.UserViewHolder>() {
class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val mName: TextView = itemView.cardname
val mImage: CircleImageView = itemView.cardprofilepicture
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val itemView =
LayoutInflater.from(parent.context).inflate(R.layout.cardview_chat, parent, false)
return UserViewHolder(itemView)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val currentItem = userList[position]
// Glide.with(UsersAdapter).load()
Picasso.get().load(currentItem.profileimageurl).into(holder.mImage)
holder.mName.text = currentItem.name
}
override fun getItemCount()=userList.size
}
No syntax error
Your method to obtain the list getUserList() is asynchronous; meaning it needs to wait for firebase to produce results.
You can see this because you do databaseReference.addListenerForSingleValueEvent... meaning you're adding a listener that will be called in the future, not synchronously.
Your method (getUserList) creates a new list, and returns it (empty).
Later, your firebase callback is called and you modify the local list; it's already "too late".
As a "quick hack" you can change the getUserList() method to something more like fun fetchResultsAndInitializeAdapter() (which is a little bit of a code smell because a function is doing "two things" but it should work). It doesn't return anything.
So inside the fetchResultsAndInitializeAdapter you can initialize the adapter after the list is populated:
override fun onDataChange(snapshot: DataSnapshot) {
val list: List<xxx>
if (snapshot.exists()) {
val item = User(
snapshot.child("name").value.toString(),
snapshot.child("profilePicture").value.toString(),
snapshot.child("phone").value.toString()
)
list.add(item)
}
adapter = YourAdapter(list)
recyclerView.adapter = adapter
}
Now, I wouldn't do this. I believe this database transaction and list mutation does not belong here, it belongs in a repo or a viewmodel that exposes a LiveData<List> that is observed by your Fragment/Activity, and when a value is emitted it is simply passed onto the Adapter.
But the whole firebase + list creation + etc. shouldn't be mixed with a Fragment's code in my opinion.
the onDataChange method is working asynchronously,, so instead of modifying local variable, you should modify class property.... move out the list into class property, and do changes it inside these methods, and don't forget to notify data changes to the adapter..
you need something like this:
class UserChatFragment : Fragment() {
lateinit var mobileno: String
val listUser: MutableList<User> = mutableListOf() // move it out here..
...
... // TLDR
// somewhere on your code
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
val item = User(
snapshot.child("name").value.toString(),
snapshot.child("profilePicture").value.toString(),
snapshot.child("phone").value.toString()
)
listUser.add(item)
adapter.notifyDatasetChanges()
}
}
...
}

RecyclerView Android Error: E/RecyclerView: No adapter attached; skipping layout

When trying to get JSON data from an API and show it on the RecyclerView I'm getting following error:
I already use RecyclerView sometimes and I never had this problem.
MainActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(activity_main)
overwriteOnPostInteractionListener()
setupObservers()
}
private fun setupObservers(){
mServiceRequest.searchPostsFromAPI().observe(this, Observer { posts ->
if (posts != null){
loadRecyclerView()
mPostList = posts.toMutableList()
}
})
}
private fun loadRecyclerView() {
recyclerView.adapter = PostListAdapter(mPostList, mOnPostListInteractionListener)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(true)
}
Adapter:
class PostListAdapter(private val postList: List<PostModel>,
private val onPostListInteractionListener: OnPostListInteractionListener):
RecyclerView.Adapter<PostViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
val inflate = LayoutInflater.from(parent.context)
val view = inflate.inflate(R.layout.posts , parent, false)
return PostViewHolder(view, parent.context, onPostListInteractionListener)
}
override fun getItemCount(): Int {
return postList.count()
}
override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
holder.bindTask(postList[position])
}}
ViewHolder:
class PostViewHolder(itemView: View, private val context: Context,
private val onPostListInteractionListener: OnPostListInteractionListener)
: RecyclerView.ViewHolder(itemView) {
private val postTitle = itemView.findViewById<TextView>(R.id.titleTextViewMain)
private val postBody = itemView.findViewById<EditText>(R.id.bodyEditText)
fun bindTask(post: PostModel){
postTitle.text = post.title
postBody.setText(post.body)
postTitle.setOnClickListener {
onPostListInteractionListener.onListClick(post.id)
}
}}
I searched a lot how to solve this error but I can't.
You build your RecyclerView adapter before feeding the data to the list, so you need to call mPostList = posts.toMutableList() before instantiating your adapter.
So, change setupObservers() with:
private fun setupObservers(){
mServiceRequest.searchPostsFromAPI().observe(this, Observer { posts ->
if (posts != null){
mPostList = posts.toMutableList()
loadRecyclerView()
}
})
}
Also set your adapter after, you completely build the RecyckerView. So change the order of loadRecyclerView() as below.
private fun loadRecyclerView() {
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(true)
recyclerView.adapter = PostListAdapter(mPostList, mOnPostListInteractionListener)
}
Setting RecyclerView adapter before layout can cause issues.
Side note:
You instantiate your adapter every time your list is changed, and that's not the right way, and it's better to create your stuff (including the adapter) only once in a lifecycle, and then make setters each time you want to change them instead of instantiating them over and over again.
So, you can instantiate your adapter only once in onCreate() method and create a method in your adapter that takes the posts setPosts(private val postList: List<PostModel>) and set them internally .. whenever you need to change adapter list, just call setPosts

Categories

Resources