I am trying to display JSON data from a dummy server (json-server) using Retrofit2 and display it in a RecyclerView placed inside a fragment. So far the following events are taking place (found out using Log infos):
Main fragment CharactersListFragment.kt creation is initiated.
Only getItemCount() is being called. It is returning 0.
OnCreateViewHolder() and onBindViewHolder() are not being called.
Data is successfully fetched from the server and passed to the adapter (from CharactersListFragment.kt)
There are no compile time errors nor does my app crash, just the RecyclerView is empty.
Here are my code files:
Layout for fragment with RecyclerView: fragment_characters_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".CharactersListFragment">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/recycler_view"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Layout for RecyclerView row: fragment_character_recyclerview_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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:id="#+id/character_card_view"
android:layout_width="match_parent"
android:layout_height="300dp"
android:elevation="15dp"
app:cardBackgroundColor="#color/violet_forcard"
app:cardCornerRadius="3dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/char_thumnail_img"
android:layout_width="143dp"
android:layout_height="150dp"
android:layout_marginStart="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.106"
tools:srcCompat="#tools:sample/avatars" />
<TextView
android:id="#+id/char_id_txt"
android:layout_width="190dp"
android:layout_height="53dp"
android:layout_marginTop="16dp"
android:fontFamily="#font/roboto"
android:text="Character ID: "
android:textAlignment="viewStart"
android:textColor="#color/white"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/char_thumnail_img"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/char_name_txt"
android:layout_width="190dp"
android:layout_height="53dp"
android:layout_marginTop="24dp"
android:fontFamily="#font/roboto"
android:text="Name: "
android:textAlignment="viewStart"
android:textColor="#color/white"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/char_thumnail_img"
app:layout_constraintTop_toBottomOf="#+id/char_id_txt" />
<TextView
android:id="#+id/char_descp_txt"
android:layout_width="359dp"
android:layout_height="wrap_content"
android:fontFamily="#font/roboto"
android:textAlignment="viewStart"
android:textColor="#color/white"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.461"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.918" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
Kotlin class for fragment: CharactersListFragment.kt
package com.example.marvelapp
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class CharactersListFragment : Fragment() {
private lateinit var charsAdapter: CharacterListAdapter
private lateinit var apiService: APIService
private var characters: MutableList<Character> = ArrayList()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
Log.i("INSIDE_FRAGMENT_ONCREATEVIEW", "Fragment creation initiated")
val v = inflater.inflate(R.layout.fragment_characters_list, container, false)
apiService = RestClient.client.create(APIService::class.java)
getCharacterList()
val recycler_view = v.findViewById(R.id.recycler_view) as RecyclerView
recycler_view.layoutManager = LinearLayoutManager(activity)
charsAdapter = CharacterListAdapter()
recycler_view.adapter = charsAdapter
return v
}
private fun getCharacterList(){
val call = apiService!!.get_characters()
call.enqueue(object: Callback<List<Character>> {
override fun onResponse(
call: Call<List<Character>>,
response: Response<List<Character>>
) {
val chars = response.body()
Log.i("get_character_succeeded_FOR_CALL", chars.toString())
if (chars != null){
characters.addAll(chars!!)
Log.i("character_ADD_CHECK_INSIDE_FRAGMENT", characters[0].toString())
charsAdapter.submitDataforcharList(characters)
}
}
override fun onFailure(call: Call<List<Character>>, t: Throwable) {
Log.e("get_character_failed", t.localizedMessage)
}
})
}
}
Kotlin class for adapter: CharacterListAdapter.kt
package com.example.marvelapp
import android.text.Layout
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.fragment_character_recyclerview_list.view.*
class CharacterListAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var charItems: List<Character> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
Log.i("INSIDE_ONCREATE_VIEWHOLDER", "reached")
return CharacterListViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.fragment_character_recyclerview_list,
parent,
false))
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder){
is CharacterListViewHolder ->{
Log.i("VIEWHOLDER_FOUND", "proceeding to bind")
holder.bind(charItems.get(position))
}
}
}
override fun getItemCount(): Int {
Log.i("INSIDE_ADAPTER_GET_ITEM_COUNT", charItems.size.toString())
return charItems.size
}
fun submitDataforcharList (characterlist: List<Character>){
Log.i("INSIDE_ADAPTER_SUBMIT_DATA", characterlist[0].toString())
charItems = characterlist
}
class CharacterListViewHolder
constructor(view: View): RecyclerView.ViewHolder(view){
val char_id = view.findViewById<TextView>(R.id.char_id_txt)
val char_name = view.findViewById<TextView>(R.id.char_name_txt)
val char_descp = view.findViewById<TextView>(R.id.char_descp_txt)
fun bind(character: Character){
Log.i("INSIDE_VIEWHOLDER_BIND", character.toString())
val id = Integer.toString(character.charId)
val id_mess = "Character ID: $id"
char_id.setText(id_mess)
val char_name_mess = "Name: ${character.charName}"
char_name.setText(char_name_mess)
char_descp.setText(character.charDescp)
}
}
}
the Character model class:
package com.example.marvelapp
data class Character(
val charId: Int,
val charName: String,
val charDescp: String,
){
}
I am an android beginner and have been stuck at this for quite some time, any help will be highly appreciated.
Thanks in advance!
In the onCreateView of CharactersListFragment you are calling getCharacterList() and then you move on with your initialization of the recycler view. After the getCharacterList call completes, you might be getting some data and you are simply updating the data list in your adapter class but you are not informing your recyclerview to refresh the view to display the new data. So I think if you just call notifyDataSetChanged() on your adapter after setting the new data it should work.
The other way to solve the issue could be if you initialize your recyclerview after you've received the data from the getCharacterList() call and just show some loading icon in place of the recycler view while the data is being fetched.
getCharacterList() is called on UI thread (main thread) but communicating (requests and responses) between device and API server is happening in another thread, it means there is no guarantee that onResponse() will come earlier than recycler_view.adapter = charsAdapter and in reality, it mostly does not happens. Therefor, you must call charsAdapter.notifyDataSetChanged() after charsAdapter.submitDataforcharList(characters) to have the recyclerview represent data. Another more elegent way to do that is adding notifyDatasetChanged() inside submitDataforcharList():
fun submitDataforcharList (characterlist: List<Character>){
charItems = characterlist
notifyDatasetChanged()
}
Related
I'm trying to create an application and considering my level it's not easy! I hope you could help me since I didn't succeed with the many links I found on the internet.
I can't add the onClick function of View.OnClickListener, each time the Intent function is not recognized. I tried to implement it in the UserViewHolder and FirestoreRecyclerAdapter class but it doesn't work.
Here is my current code:
---------- kotlin part ---------
package edu.stanford.rkpandey.emojistatus
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.*
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.firebase.ui.firestore.FirestoreRecyclerAdapter
import com.firebase.ui.firestore.FirestoreRecyclerOptions
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.firestore.ktx.firestore
import com.google.firebase.ktx.Firebase
import kotlinx.android.synthetic.main.activity_main.*
data class User(
val displayName: String? = "",
val emojis: String? = ""
)
class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
class MainActivity : AppCompatActivity() {
private val db = Firebase.firestore
private lateinit var auth: FirebaseAuth
// Query the users collection
private val query = db.collection("users")
val options = FirestoreRecyclerOptions.Builder<User>()
.setQuery(query, User::class.java)
.setLifecycleOwner(this).build()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
auth = Firebase.auth
val adapter = object: FirestoreRecyclerAdapter<User, UserViewHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val view = LayoutInflater.from(this#MainActivity).inflate(R.layout.item_pack, parent, false)
return UserViewHolder(view)
}
override fun onBindViewHolder(
holder: UserViewHolder,
position: Int,
model: User
) {
val tvName: TextView = holder.itemView.findViewById(R.id.title)
val tvEmojis: TextView = holder.itemView.findViewById(R.id.excerpt)
tvName.text = model.displayName
tvEmojis.text = model.emojis
}
}
rvUsers.adapter = adapter
rvUsers.layoutManager = LinearLayoutManager(this)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.miLogout) {
Log.i("MainActivity", "Logout")
auth.signOut()
val logoutIntent = Intent(this, LoginActivity::class.java)
logoutIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(logoutIntent)
}
return super.onOptionsItemSelected(item)
}
}
------- xml part -------
=> activity_main
<?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/rvUsers"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
=> item_pack
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="100sp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.cardview.widget.CardView
android:id="#+id/card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="12sp"
android:layout_marginTop="12sp"
android:layout_marginEnd="12sp"
android:focusable="true"
android:clickable="true"
app:cardCornerRadius="10dp"
android:foreground="?android:attr/selectableItemBackground">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:background="#color/colorPack">
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5sp"
style="#style/NoteTitleFont"
android:textColor="#color/colorTitle"
tools:text="Note 1" />
<TextView
android:id="#+id/excerpt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12sp"
android:layout_below="#id/title"
android:maxLines="1"
android:ellipsize="end"
android:textStyle="italic"
android:textColor="#color/colorDescribe"
tools:text="test text va se trouver ici, ça va contenir le début de la description du package." />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
This code gives this result :
I would like that when I click on one of the carviews it can go to the activity_pack_detail.
Do you know how to do Intent to PackDetailActivity?
I get this error no matter what I do =>
You're getting that error because you are calling Intent's class constructor with a wrong argument. The first argument should be a Context and not a View. The keyword this is referring in your code to a View and not to a context, hence the error.
To solve this, you have to pass a Context object like this:
val i = Intent(view.getContext(), PackDetailActivity::class.java)
And your error will go away.
I am trying to show a list of transactions in a recycler view.
Each transaction is represented by a CardView inside a constraint layout (see item_transaction.xml).
Somehow, the functions of my RecyclerView Adapter (onCreateViewHolder, onBindViewHolder and getItemCount) are never called (logs are never displayed - I removed most of them so the code is easier to read).
Therefore, the RecyclerView content doesn't display on my app.
It is also worth pointing out that I have a list of transactions called data in my adapter. Whenever I set the data in the list, it does update. No issues there.
💡 I know notifyDataSetChanged() isn't clean, I'll change it later on when I manage to get everything working.
Here is a preview of what I want so you can grasp the idea better :
EDIT : My fragment displays normally and has no problem executing code.
Hang on tight because there is a lot of code :
package com.example.manage.manageit.database
class Transaction(
var value: Long = 0,
var note: String = "",
var budget: String = "",
var creationTime: Long = System.currentTimeMillis()
)
Transaction.kt
package com.example.manage.manageit.home
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.example.manage.manageit.databinding.ActivityHomeBinding
import androidx.fragment.app.Fragment
import com.example.manage.manageit.R
import com.example.manage.manageit.about.AboutFragment
import com.example.manage.manageit.budget.TransactionFragment
class HomeActivity : AppCompatActivity() {
private lateinit var binding: ActivityHomeBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_home)
binding.bottomNavigation.menu.getItem(0).isCheckable = true
setFragment(HomeFragment())
binding.bottomNavigation.setOnItemSelectedListener {menu ->
when(menu.itemId){
R.id.homeFragmentButton -> {
setFragment(HomeFragment())
true
}
R.id.transactionFragmentButton -> {
setFragment(TransactionFragment())
true
}
R.id.aboutFragmentButton -> {
setFragment(AboutFragment())
true
}
else -> false
}
}
}
private fun setFragment(fr : Fragment){
val frag = supportFragmentManager.beginTransaction()
frag.replace(R.id.myNavHostFragment,fr)
frag.commit()
}
}
HomeActivity.kt : It contains a BottomNavigationView and my fragment.
package com.example.manage.manageit.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.manage.manageit.R
import com.example.manage.manageit.adapters.TransactionAdapter
import com.example.manage.manageit.database.Transaction
import com.example.manage.manageit.databinding.FragmentHomeBinding
import java.util.logging.Logger
class HomeFragment : Fragment() {
private var logger = Logger.getLogger(HomeFragment::class.java.name)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: FragmentHomeBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_home,
container,
false
)
val recyclerView: RecyclerView = binding.transactionList
val transactionAdapter = TransactionAdapter()
recyclerView.adapter = transactionAdapter
recyclerView.layoutManager = LinearLayoutManager(context)
transactionAdapter.data = listOf(
Transaction(10, "Transaction note", "Groceries"),
Transaction(20, "Transaction note", "Groceries"),
Transaction(30, "Transaction note", "Groceries")
)
return inflater.inflate(R.layout.fragment_home, container, false)
}
}
HomeFragment.kt
package com.example.manage.manageit.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.manage.manageit.R
import com.example.manage.manageit.database.Transaction
import java.util.logging.Logger
class TransactionAdapter : RecyclerView.Adapter<TransactionViewHolder>() {
private var logger = Logger.getLogger(this::class.java.name)
var data = listOf<Transaction>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionViewHolder {
logger.info("adapter : onCreateViewHolder()")
return TransactionViewHolder.from(parent)
}
override fun onBindViewHolder(holder: TransactionViewHolder, position: Int) {
val item = this.data[position]
holder.bind(item)
}
override fun getItemCount(): Int {
logger.info("adapter : getItemCount()")
return this.data.size
}
}
class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val icon: ImageView = itemView.findViewById(R.id.transaction_icon)
private val note: TextView = itemView.findViewById(R.id.transaction_note)
private val value: TextView = itemView.findViewById(R.id.transaction_value)
fun bind(item: Transaction) {
icon.setImageResource(R.drawable.ic_dollar_sign)
note.text = item.note
value.text = item.value.toString()
}
companion object {
fun from(parent: ViewGroup): TransactionViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.item_transaction, parent, false)
return TransactionViewHolder(view)
}
}
}
TransactionAdapter.kt
<?xml version="1.0" encoding="utf-8"?>
<layout 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"
tools:context="com.example.manage.manageit.home.HomeActivity">
<RelativeLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/myNavHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"/>
</LinearLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#android:color/black"
android:paddingVertical="6dp"
app:itemIconSize="28dp"
app:itemIconTint="#color/nav_bar_item"
app:itemRippleColor="#color/ripple_color"
app:itemTextColor="#color/nav_bar_item"
app:labelVisibilityMode="unlabeled"
app:menu="#menu/bottom_navigation_menu" />
</RelativeLayout>
</layout>
activity_home.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/black"
android:fillViewport="true"
tools:context="com.example.manage.manageit.home.HomeFragment">
<TextView
android:id="#+id/homeActivityTitle"
style="#style/activityTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/medium_margin"
android:layout_marginTop="#dimen/medium_margin"
android:layout_marginEnd="#dimen/medium_margin"
android:text="#string/home_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/transaction_list"
android:layout_width="match_parent"
android:layout_height="400dp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="#+id/floatingActionButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/homeActivityTitle" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="#dimen/medium_margin"
android:layout_marginBottom="#dimen/very_big_margin"
android:backgroundTint="#color/secondaryColor"
android:clickable="true"
android:contentDescription="#string/add_button_description"
android:src="#drawable/ic_plus"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:maxImageSize="32dp"
tools:ignore="RedundantDescriptionCheck" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
fragment_home.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="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
app:cardBackgroundColor="#android:color/transparent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#drawable/transaction">
<ImageView
android:id="#+id/transaction_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:contentDescription="Icon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_dollar_sign"
app:tint="#android:color/white" />
<TextView
android:id="#+id/transaction_note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:text="Transaction note"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="#+id/transaction_icon"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/transaction_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="30dp"
android:text="55€"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
item_transaction.xml : Basically a card view with an image and two text views (the transaction "note" and value).
In onCreateView, you have created your view layout twice. The first one you did using DataBindingUtil and that's the one whose RecyclerView you set up. But then you let that whole layout go back to the garbage collector because you create a brand new layout using layoutInflater and return that layout on the last line of onCreateView.
Technically, you should not be setting up views in onCreateView anyway. It should be done in onViewCreated(), although I don't think it makes much difference. However, since Fragment provides a constructor that can automatically inflate a provided layout ID, I think it's cleaner anyway to eliminate onCreateView() entirely, like this:
class HomeFragment : Fragment(R.layout.fragment_home) {
private var logger = Logger.getLogger(HomeFragment::class.java.name)
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
val binding: FragmentHomeBinding = DataBindingUtil.bind(view)
val recyclerView: RecyclerView = binding.transactionList
val transactionAdapter = TransactionAdapter()
recyclerView.adapter = transactionAdapter
recyclerView.layoutManager = LinearLayoutManager(context)
transactionAdapter.data = listOf(
Transaction(10, "Transaction note", "Groceries"),
Transaction(20, "Transaction note", "Groceries"),
Transaction(30, "Transaction note", "Groceries")
)
}
}
i'd like to know what i'm doing wrong over here. I'm building a CRUD app in Kotlin, and i'm using the recyclerview to make the readData page. The problem is, when i'm on the readData page doesn't show me anything, just the text views, so i debugged and when i join in the page, show this message: "Recyclerview No adapter attached; skipping layout".
Here's my code:
(i'm brazilian, so, some words are in portuguese, but you'll get it.)
verDados.kt
package com.nicolas.csrd
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import com.google.firebase.database.*
import kotlinx.android.synthetic.main.activity_ver_database.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class verDados : AppCompatActivity() {
private lateinit var database: FirebaseDatabase
private lateinit var reference: DatabaseReference
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_ver_database)
database = FirebaseDatabase.getInstance()
reference = database.getReference("usuarios")
verDados()
btn_voltar.setOnClickListener() {
startActivity(Intent(this#verDados, Dashboard::class.java))
finish()
}
}
private fun verDados() {
reference.addValueEventListener(object: ValueEventListener{
override fun onCancelled(p0: DatabaseError) {
Log.e("cancelar", p0.toString())
}
override fun onDataChange(p0: DataSnapshot) {
//Colocando os usuarios numa lista
var list = ArrayList<DatabaseModelo>()
for (data in p0.children) {
val model = data.getValue(DatabaseModelo::class.java)
list.add(model as DatabaseModelo)
}
if (list.size > 0) {
val ususariosModelo = usuariosModelo(list)
recyclerview.adapter = ususariosModelo
}
}
})
}
}
DatabaseModel
package com.nicolas.csrd
class DatabaseModelo() {
lateinit var email: String
lateinit var senha: String
constructor(email: String, senha: String) : this() {
this.email = email
this.senha = senha
}
}
usuariosModelo.kt (recyclerview adapter)
package com.nicolas.csrd
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.menu.ActionMenuItemView
import androidx.appcompat.view.menu.MenuView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.usuarios_modelo.view.*
class usuariosModelo(val list: ArrayList<DatabaseModelo>): RecyclerView.Adapter<usuariosModelo.ViewHolder>() {
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val email = itemView.campo_email
val senha = itemView.campo_senha
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.usuarios_modelo, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.email.text = list[position].email
holder.senha.text = list[position].senha
}
override fun getItemCount(): Int {
return list.size
}
}
----- XML FILES -----
activity_ver_database.xml (read data page)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/dark"
tools:context=".Login">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal" >
<ImageView
android:id="#+id/btn_voltar"
android:layout_width="50dp"
android:layout_height="30dp"
android:layout_gravity="start"
android:layout_marginStart="5dp"
android:layout_marginTop="23dp"
android:src="#drawable/back_arrow" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DADOS"
android:textColor="#7ec1d1"
android:textSize="30sp"
android:textStyle="bold"
android:layout_marginTop="20dp"
android:letterSpacing="0.15"
android:fontFamily="#font/montserrat_medium"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Todos os usuários cadastrados"
android:textColor="#7ec1d1"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginTop="0dp"
android:fontFamily="#font/montserrat_thin"
/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/recyclerview"
android:layout_margin="10sp"/>
<TextView
android:id="#+id/btn_cadastrar"
android:layout_width="350dp"
android:layout_height="50dp"
android:layout_marginTop="30dp"
android:backgroundTint="#7ec1d1"
android:gravity="center_horizontal"
android:fontFamily="#font/montserrat_regular"
android:text="Deseja cadastrar alguém? Clique aqui."
android:textColor="#color/white2"
android:textSize="15sp" />
</LinearLayout>
</LinearLayout>
You are getting this error because you have no defined an adaptor for the RecyclerView that you are using. Somewhere inn your onCreate method, create the adaptor and set it to the RecyclerView. Something like:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_ver_database)
recyclerView = findViewById(R.id.recyclerview)
recyclerView.apply {
adapter = usuariosModelo(listOfItems)
}
database = FirebaseDatabase.getInstance()
reference = database.getReference("usuarios")
verDados()
btn_voltar.setOnClickListener() {
startActivity(Intent(this#verDados, Dashboard::class.java))
finish()
}
}
i solve the problem just adding the tag below inside the recyclerview tag in the .xml file:
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
I hope that help y'all!
I am trying to implement onEditPost and onDeletePost inside of my RecyclerViewFragment:
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.MainActivity
import com.example.projectdrivemark.R
import com.example.projectdrivemark.databinding.FragmentListBinding
import com.example.projectdrivemark.databinding.PostBinding
class RecyclerViewFragment: Fragment(R.layout.fragment_list), PostAdapter.OnPostClickListener {
private lateinit var binding: FragmentListBinding
val dummyList = MockDatabase.createMockData()
val adapter = PostAdapter(dummyList, this)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreate(savedInstanceState)
binding = FragmentListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<RecyclerView>(R.id.recycleViewMain).apply{
layoutManager = LinearLayoutManager(requireActivity())
adapter = PostAdapter(dummyList, this#RecyclerViewFragment)
}
}
override fun onEditPost(position: Int){
val clickedPost = dummyList[position]
clickedPost.title = "Updated title"
clickedPost.body = "Updated body"
adapter.notifyItemChanged(position)
}
override fun onDeletePost(position: Int) {
dummyList.removeAt(position)
adapter.notifyItemRemoved(position)
}
}
Here is how my Adapter looks like:
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.MainActivity
import com.example.projectdrivemark.R
class PostAdapter(val dummyData: ArrayList<Post>, val myListener: OnPostClickListener) : RecyclerView.Adapter<PostAdapter.PostViewHolder>() {
inner class PostViewHolder(postView: View) : RecyclerView.ViewHolder(postView), View.OnClickListener{
val iconImage: ImageView = postView.findViewById(R.id.icon_image_view)
val title: TextView = postView.findViewById(R.id.title)
val body: TextView = postView.findViewById(R.id.body)
val deleteIcon: ImageView = postView.findViewById(R.id.delete_post_image)
val editIcon: ImageView = postView.findViewById(R.id.edit_post_image)
init {
deleteIcon.setOnClickListener(this)
editIcon.setOnClickListener(this)
}
override fun onClick(v: View?){
val position = adapterPosition
if(v?.id == editIcon.id){
myListener.onEditPost(position)
}else{
myListener.onDeletePost(position)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
val postView = LayoutInflater.from(parent.context).inflate(R.layout.post, parent, false)
return PostViewHolder(postView)
}
override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
val currentPost = dummyData[position]
holder.iconImage.setImageResource(currentPost.image)
holder.title.text = currentPost.title
holder.body.text = currentPost.body
}
override fun getItemCount(): Int {
return dummyData.size
}
interface OnPostClickListener{
fun onEditPost(position: Int)
fun onDeletePost(position: Int)
}
}
And here is how my XML File Post looks like:
<?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="wrap_content"
android:layout_marginBottom="8dp"
android:padding="5dp">
<ImageView
android:id="#+id/icon_image_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:src="#drawable/ic_baseline_ac_unit"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp"
android:text="Title of blog post"
android:textColor="#android:color/black"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="#+id/textView2"
app:layout_constraintStart_toEndOf="#+id/icon_image_view"
app:layout_constraintTop_toTopOf="#+id/icon_image_view" />
<TextView
android:id="#+id/body"
android:layout_width="285dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:text="Body of first blog post"
app:layout_constraintBottom_toBottomOf="#+id/icon_image_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.050"
app:layout_constraintStart_toEndOf="#+id/icon_image_view" />
<LinearLayout
android:id="#+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="end">
<ImageView
android:id="#+id/edit_post_image"
android:layout_width="60dp"
android:layout_height="50dp"
android:src="#drawable/ic_baseline_edit" />
<ImageView
android:id="#+id/delete_post_image"
android:layout_width="60dp"
android:layout_height="50dp"
android:src="#drawable/ic_baseline_delete_forever" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
As you see in the RecyclerViewFragment, I have 2 functions that is onEditPost and onDeletePost. I want that functionality to work when I press it. Right now, it only works when I press and change the fragment view. I want it to work instantly. If anyone see my mistake or Am I missing something? please do tell me. All help is appreciate
You are creating Adapter twice, you already have an Adapter initialised globally use it in onViewCreated instead of assign again. because you are using global adapter to notify changes.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<RecyclerView>(R.id.recycleViewMain).apply{
layoutManager = LinearLayoutManager(requireActivity())
adapter = this#RecyclerViewFragment.adapter
}
}
I'm trying to implement the RecyclerView with data binding.
The RecyclerView worked properly when I used findViewById to retrieve the widgets. But, if I use RecyclerView with data binding, the data are not bound to the ViewHolder.
MainActivity.kt
package com.example.wifilogger
import android.os.Bundle
import android.os.Handler
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.wifilogger.databinding.ActivityMainBinding
import com.example.wifilogger.helper.WiFi
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private var wifiList = ArrayList<WiFi>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.loading = true
wifiList.add(WiFi("38:BC:01:64:2D:38", "Example", -87, "WPA2"))
recycler_view.apply {
layoutManager = LinearLayoutManager(context)
adapter = WiFiAdapter(wifiList)
}
Handler().postDelayed({
recycler_view.adapter?.notifyDataSetChanged()
}, 1000)
}
}
WiFi.kt
data class WiFi(var BSSID: String, var SSID: String, var level: Int, var encryption: String)
WiFiAdapter
package com.example.wifilogger
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import com.example.wifilogger.databinding.ListItemBinding
import com.example.wifilogger.helper.WiFi
class WiFiAdapter(_wifiList: ArrayList<WiFi>) :
androidx.recyclerview.widget.RecyclerView.Adapter<WiFiAdapter.ViewHolder>() {
private val wifiList = _wifiList
class ViewHolder(_binding: ListItemBinding) : androidx.recyclerview.widget.RecyclerView.ViewHolder(_binding.root) {
var binding: ListItemBinding = _binding
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ListItemBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.list_item, parent, false
)
return ViewHolder(binding)
}
override fun getItemCount() = wifiList.size
override fun onBindViewHolder(holder: ViewHolder, pos: Int) {
holder.binding.ssid.text = wifiList[pos].SSID
holder.binding.bssid.text = wifiList[pos].BSSID
holder.binding.level.text = wifiList[pos].level.toString()
holder.binding.encryption.text = wifiList[pos].encryption
}
}
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tool="http://schemas.android.com/tools">
<data>
<variable
name="ssid"
type="String" />
<variable
name="bssid"
type="String" />
<variable
name="level"
type="int" />
<variable
name="encryption"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:paddingTop="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/ssid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:textColor="#color/md_grey_900"
android:text="#{ssid}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tool:hint="SSID: Mum Use This One" />
<TextView
android:id="#+id/bssid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/md_grey_600"
android:text="#{bssid}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/ssid"
tool:hint="BSSID: 4A-C0-D0-07-91-E5" />
<TextView
android:id="#+id/level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:textColor="#color/md_grey_600"
android:text='#{level + "dBm"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tool:hint="-68 dBm" />
<TextView
android:id="#+id/encryption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/md_grey_600"
android:text="#{encryption}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/level"
tool:hint="WPA2" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</layout>
The code works if I call notifyDataSetChanged() with a small delay.
Screenshot
But, if I remove that line, the data is not bound to the list_item.
Screenshot
I think some asynchronous task is running when binding the data to the ViewHolder, but I can't figure out where.
Any help would be appreciated. Thank you.
Try
class WiFiAdapter(_wifiList: ArrayList<WiFi>) :
androidx.recyclerview.widget.RecyclerView.Adapter<WiFiAdapter.ViewHolder>() {
...
override fun onBindViewHolder(holder: ViewHolder, pos: Int) {
...
holder.binding.executePendingBindings()
}
}