I have created a recycle view and inside that using card view for items. I have a delete button inside a card view whenever I click on that button my item is deleted from SQLite database. But to reflect it on UI, app need to restart. How can I notify adpater that item is deleted?
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="57dp"
android:layout_height="64dp"
android:layout_marginEnd="40dp"
android:layout_marginBottom="40dp"
android:clickable="true"
android:onClick="addNewCredentials"
app:backgroundTint="#270867"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="#android:drawable/ic_menu_add" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="1dp"
android:layout_marginTop="1dp"
android:layout_marginEnd="1dp"
android:layout_marginBottom="1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" >
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
list_item_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/urlView"
android:layout_width="300dp"
android:layout_height="30dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="url"
android:textAppearance="#style/TextAppearance.AppCompat.Large"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="#+id/userNameView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
<TextView
android:id="#+id/userNameView"
android:layout_width="300dp"
android:layout_height="25dp"
android:layout_marginBottom="16dp"
android:text="userName"
app:layout_constraintBottom_toTopOf="#+id/passwordView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.1"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="#+id/passwordView"
android:layout_width="300dp"
android:layout_height="25dp"
android:layout_marginBottom="16dp"
android:text="password"
app:layout_constraintBottom_toTopOf="#+id/noteView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.1"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="#+id/noteView"
android:layout_width="300dp"
android:layout_height="30dp"
android:layout_marginBottom="16dp"
android:text="note"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.1"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="#+id/delButton"
android:layout_width="78dp"
android:layout_height="40dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="36dp"
android:background="#E6360F"
android:text="#string/delete_credential_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
MainActivity.kt
package com.example.passwordmanager
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL,false)
val db = DataBaseHandler(this)
val detailsData = db.readCredentials()
val adapter = CredentialAdapter(detailsData,this,{credentialsModel: CredentialsModel->deleteClick(credentialsModel)})
recyclerView.adapter = adapter
}
fun deleteClick(credential: CredentialsModel){
val db = DataBaseHandler(this)
if(db.deleteData(credential.id)){
//adapter.notifyItemRemoved(position)
Toast.makeText(applicationContext,"Deleted", Toast.LENGTH_SHORT).show()
}
}
fun addNewCredentials(view : View){
print("hello world")
val intent = Intent(this, AddDetailActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
}
}
CredentialAdapter.kt
package com.example.passwordmanager
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.list_item_layout.view.*
class CredentialAdapter(
private val items: List<CredentialsModel>,
ctx: Context, val clickListener: (CredentialsModel) -> Unit
): RecyclerView.Adapter<CredentialAdapter.ViewHolder>() {
var context = ctx
class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
fun bind(credential: CredentialsModel,clickListener: (CredentialsModel) -> Unit){
itemView.urlView.text = credential.url
itemView.userNameView.text = credential.userName
itemView.passwordView.text = credential.password
itemView.noteView.text = credential.note
itemView.delButton.setOnClickListener{clickListener(credential)}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item_layout,parent,false))
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val credential:CredentialsModel = items[position]
holder.bind(credential,clickListener)
}
}
add remove setOnClickListener in your onBindViewHolder.
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.remove.setOnClickListener {
val db = DataBaseHandler(this)
if(db.deleteData(credential.id)){
notifyItemRemoved(holder.getAdapterPosition())
}
}
}
The best way to handle these kinds of situations is to use LiveData.
LiveData is basically an observable class which reads data only when there is a change.
What you can do is create a set function in your adapter like:
internal fun setData(data: List<Data>) {
this.data= dataList //this datalist is a list defined in your adapter
notifyDataSetChanged()
}
now in your main activity/fragment, create a LiveData List outside the onCreate function like this:
private lateinit var allData:LiveData<List<Data>>
Now inside your onCreate function, use can observe the livedata and set the data for recyclerview like this:
allData.observe(this, Observer { data->
data?.let { adapter.setData(it) }
})
You are deleting the item from database but not from the list inside recyclerview adapter.
class CredentialAdapter(
private val items: ArrayList<CredentialsModel>, // Change list to arraylist
ctx: Context, val clickListener: (CredentialsModel, Int) -> Unit
): RecyclerView.Adapter<CredentialAdapter.ViewHolder>() {
...
...
fun remove(position: Int) {
// Remove and notify the adapter to reload
items.removeAt(position)
notifyItemRemoved(position)
}
class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView) {
fun bind(credential: CredentialsModel,clickListener: (CredentialsModel, Int) -> Unit){
...
...
// Pass adapter item position so that we can update the list after delete
itemView.delButton.setOnClickListener{clickListener(credential, adapterPosition)
}
}
...
...
}
Inside MainActivity.kt
fun deleteClick(credential: CredentialsModel, position: Int) {
val db = DataBaseHandler(this)
if(db.deleteData(credential.id)){
adapter.remove(position)
Toast.makeText(applicationContext,"Deleted", Toast.LENGTH_SHORT).show()
}
}
use ListAdpater
class AdapterMain(var onClickListener: (Int) -> Unit) :
ListAdapter<Note, AdapterMain.NoteViewHolder>(DIFFCALBACK) {
companion object DIFFCALBACK : DiffUtil.ItemCallback<Note>() {
override fun areItemsTheSame(oldItem: Note, newItem: Note): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Note, newItem: Note): Boolean {
return oldItem.title == newItem.title &&
oldItem.description == newItem.description &&
oldItem.priority == newItem.priority
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.note_item, parent, false)
return NoteViewHolder(view)
}
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
holder.txtTitle.text = getItem(position).title
holder.txtDesc.text = getItem(position).description
holder.txtPriority.text = getItem(position).priority.toString()
}
inner class NoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var txtTitle: TextView = itemView.txt_title
var txtDesc: TextView = itemView.txt_desc
var txtPriority: TextView = itemView.txt_priority
init {
itemView.setOnClickListener { onClickListener(adapterPosition) }
}
}
fun getNoteAt(position: Int): Note {
return getItem(position)
}
}
you can see complete code of simple NoteApp with kotlin , recyclerView , MVVM and..
class coba : AppCompatActivity() {
private lateinit var recycleView :RecyclerView
private lateinit var datalis :ArrayList
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_coba)
recycleView = findViewById(R.id.rcycoba)
datalis = ArrayList()
val dtnama = arrayOf(
"Danial Sanganus",
"Bijonia Skolin",
"Alianes Pertoli",
"Sivanian Pertici",
"Olehsan alausi"
)
for (i in dtnama.indices){
datalis.add(
dataCoba(
dtnama[i]
)
)
populateData()
}
}
private fun populateData(){
val linearManager = LinearLayoutManager(this)
linearManager.reverseLayout=true
linearManager.stackFromEnd=true
recycleView.layoutManager=linearManager
val adp =adpCoba(this,datalis)
recycleView.adapter=adp
}
}
Related
I'm using Kotlin to create an event that generates a toast message on click of a recyclerview. I run into trouble making a Tost message in a recyclerview event.
I tried the following page, but couldn't solve it.
Toast message is not working in Recycler View
error code is
in kotlin & None of the following functions can be called with the arguments supplied: public open fun makeText(p0: Context!, p1: CharSequence!, p2: Int): Toast! defined in android.widget.Toast public open fun makeText(p0: Context!, p1: Int, p2: Int): Toast! defined in android.widget.Toast
PrintActivity.kt
package com.questionbank
class PrintActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vBinding = ActivityPrintBinding.inflate(layoutInflater)
setContentView(vBinding.root)
val helper = SqliteHelper(this, "myDB.sql", 1)
var recyclerViewAdapter = CustomAdapter()
recyclerViewAdapter.listData = helper.select()
vBinding.myRecyclerView.adapter = recyclerViewAdapter
vBinding.myRecyclerView.layoutManager = LinearLayoutManager(this)
vBinding.myRecyclerView.addItemDecoration(
DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
)
}
class CustomAdapter : RecyclerView.Adapter<CustomAdapter.Holder>() {
var listData = ArrayList<questionType>()
inner class Holder(val vBinding: QuestionLayoutRecyclerBinding) :
RecyclerView.ViewHolder(vBinding.root) {
fun setData(id:Int?, question: String, answer: String, exp: String) {
vBinding.printId.text=id.toString()
vBinding.myLinear.setOnClickListener {
// error occur
Toast.makeText(this#PrintActivity, "test", Toast.LENGTH_SHORT).show()
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val vBinding = QuestionLayoutRecyclerBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return Holder(vBinding)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val question = listData[position]
holder.setData(question.id, question.question, question.answer, question.exp)
}
override fun getItemCount(): Int {
return listData.size
}
}
}
activity_print
<?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=".PrintActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/myRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="32dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
question_layout_recycler.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/myLinear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="#+id/printId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView" />
</LinearLayout>
Two ways to fix,
Make CustomAdapter class as inner class.
innner class CustomAdapter : RecyclerView.Adapter<CustomAdapter.Holder>() {
So toast function it will take constant from activity class.
In viewholder, get context from view. it.context will get context from linearlayout.
vBinding.myLinear.setOnClickListener {
Toast.makeText(it.context, "test", Toast.LENGTH_SHORT).show()
}
Its recommended to place adapter logic in separate file and use second solution.
So you dont need to make adapter as inner class.
I hope is all well with you.
I have constructed my code and build according the MVP style as well pulling JSON data but when I run the the code the horizontal recycle view and the JSON data is not being displayed. I tried going through every line of code and watching other tutorials but still no results.
Here is below my main activity:
class ProductCategoryActivity : BaseMvpActivity<ProductCategoryActivityView, ProductCategoryActivityPresnter> (),
ProductCategoryActivityView, CategoryAdapter.onItemClickListener{
private lateinit var binding: FragmentProductCategoryBinding
val data :MutableList<CateogryResponse> = ArrayList()
val adapter= CategoryAdapter(data, this)
#Inject
lateinit var presenter: ProductCategoryActivityPresnter
#Inject
lateinit var progressDialog: ProgressDialog
override fun onCreateComponent() {
userComponent.plus(CategoryActivityModule(this)).inject(this)
}
override fun providePresenter(): ProductCategoryActivityPresnter {
return presenter
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = FragmentProductCategoryBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
presenter.fetchcatogries()
showdata(ArrayList())
}
override fun showError(message: String) {
}
override fun showProgress() {
}
override fun hideProgress() {
}
override fun showdata(data: ArrayList<CateogryResponse>) {
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerViewPrimary.layoutManager = layoutManager
recyclerViewPrimary.setHasFixedSize(true)
recyclerViewPrimary.adapter = adapter
for(i in data)
data.add(CateogryResponse("product"))
}
override fun onItemClick(position: Int) {
Toast.makeText(this, "Item $position clicked", Toast.LENGTH_SHORT).show()
val clickedItem = data[position]
adapter.notifyItemChanged(position)
}
}
Here is my presenter:
#ActivityScope
class ProductCategoryActivityPresnter #Inject constructor(
private val stringProvider: StringProvider,
#AndroidScheduler private val observeOnScheduler: Scheduler,
#IOScheduler private val subscribeOnScheduler: Scheduler,
private val getCategoryUseCase: CategoryUseCase
) : BasePresenter<ProductCategoryActivityView>() {
lateinit var catogriesservice: CategoryRepositoryImpl
val catogriesLoadError = MutableLiveData<Boolean>()
val loading = MutableLiveData<Boolean>()
var catogries: ArrayList<CateogryResponse> = arrayListOf()
override fun onCreatePresenter(savedInstanceState: Bundle?) {
}
override fun onSaveInstanceState(outState: Bundle?) {
}
override fun onLoadData(arguments: Bundle?) {
fetchcatogries()
}
fun fetchcatogries() {
getCategoryUseCase.execute()
.observeOn(observeOnScheduler)
.subscribeOn(subscribeOnScheduler)
.subscribe(
SingleRequestSubscriber(
{
it
if (it != null && it.size > 0) {
catogries.removeAll(catogries)
catogries.addAll(it)
}
},
onFailure = { appException ->
view?.showError(
ErrorHandler.getErrorMessage(
appException,
stringProvider
)
)
},
onApiError = { apiException ->
view?.showError(
ErrorHandler.getErrorMessage(
apiException,
stringProvider
)
)
},
onAuthenticationError = { requestxception ->
view?.showError(
ErrorHandler.getErrorMessage(
requestxception,
stringProvider
)
)
},
onShowProgress = {
if (it) {
view?.showProgress()
} else {
view?.hideProgress()
}
},
onSubscribed = {
disposable.add(it)
})
)
}
}
Here is my view:
interface ProductCategoryActivityView {
fun showError(message: String)
fun showProgress()
fun hideProgress()
fun showdata ( data: ArrayList<CateogryResponse>)
}
Here is my adapter:
class CategoryAdapter(
private val data: List<CateogryResponse>,
private val listener: onItemClickListener
) : RecyclerView.Adapter<CategoryAdapter.ViewHolder>() {
private val items: MutableList<CardView>
init {
this.items = ArrayList()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_category_adapter, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvTitle.text = data[position].product
items.add(holder.card)
}
override fun getItemCount(): Int {
return data.size
}
inner class ViewHolder(itemView: View
) : RecyclerView.ViewHolder(itemView),
View.OnClickListener {
val tvTitle: TextView = itemView.featured_title
val card: CardView = itemView.CardView
init {
itemView.setOnClickListener(this)
}
override fun onClick(v: View?) {
val position: Int = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(position)
}
}
}
interface onItemClickListener {
fun onItemClick(position: Int)
}
}
Image:
Fragment Category Adapter:
android:id="#+id/CardView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:cardCornerRadius="2dp"
app:cardElevation="8dp">
<!-- We Will Add here the card & ImageViews -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="15dp">
<ImageView
android:id="#+id/featured_image"
android:layout_width="match_parent"
android:layout_height="140dp"
android:scaleType="centerCrop" />
<TextView
android:id="#+id/featured_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lineHeight="23dp"
android:text="Chairs"
android:textColor="#color/colorAccent"
android:textSize="20sp" />
<TextView
android:id="#+id/featured_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="asbkd asudhlasn saudnas jasdjasl hisajdl asjdlnas" />
</LinearLayout>
</androidx.cardview.widget.CardView>
Fragment Product Category:
<?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"
android:background="#fafcfe"
tools:context="ui.category.ProductCategoryActivity">
<ImageView
android:id="#+id/imageView3"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>
<ImageView
android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="#+id/imageView"
app:layout_constraintTop_toTopOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerViewPrimary"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:layout_marginTop="20dp"
android:text="#string/topselling"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/constraintLayout" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout2"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="32dp"
android:layout_marginLeft="32dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toTopOf="#+id/imageView3"
app:layout_constraintEnd_toEndOf="#+id/constraintLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerViewSecondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="#+id/imageView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
app:layout_constraintBottom_toBottomOf="#+id/imageView3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
/>
<TextView
android:id="#+id/textViewCategories"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="15dp"
android:text="#string/catogries"
android:textSize="18sp"
app:layout_constraintBottom_toTopOf="#+id/constraintLayout"
app:layout_constraintStart_toStartOf="parent"
tools:layout_editor_absoluteX="39dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
Not sure what went wrong.
If someone can point it out or show me what needs to be done to be able to display the data in horzintal recycle view, I would be really thanlful
You should update your recycler view adapter once your fetch call is executed, not before it ends.
Modify your fetchcatogries declaration so it accepts a callback method, which will be executed after the data are loaded.
This method will accept a list object as a parameter, which you will manage in your ProductCategoryActivity to populate the RecyclerView.
fun fetchcatogries(callback: (MutableList<CateogryResponse>) -> Unit) {
getCategoryUseCase.execute()
.observeOn(observeOnScheduler)
.subscribeOn(subscribeOnScheduler)
.subscribe(
SingleRequestSubscriber(
{ categories ->
callback(categories)
},
...
)
}
You can now edit your onCreate and showdata methods in ProductCategoryActivity like this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = FragmentProductCategoryBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
presenter.fetchcatogries { categories ->
showdata(categories as ArrayList<CateogryResponse>)
}
}
override fun showdata(data: ArrayList<CateogryResponse>) {
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerViewPrimary.layoutManager = layoutManager
recyclerViewPrimary.setHasFixedSize(true)
recyclerViewPrimary.adapter = CategoryAdapter(data.toList(), this)
}
I am trying to create a recycler listview that houses integer numbers between 1 and 250.
The issue I am having is that when I scroll through the list, it only displays 1-9 and then randomly shows only single digits. Is it recycling the cached item values?
Here is how my adapter looks:
package com.work.me
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.counter_layout.view.*
class CounterAdapter : RecyclerView.Adapter<CounterAdapter.ViewHolder>() {
private val counterList = mutableListOf<Int>()
init {
for (x in 0..COUNTER_MAX) {
Log.d("JJJ", "x is $x")
counterList.add(x + COUNTER_OFFSET)
}
}
inner class ViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
fun bindView(counterValue: String) {
Log.d("JJJ", "counterValue is $counterValue")
view.counterText.text = counterValue
Log.d("JJJ", " view.counterText is ${view.counterText.text}")
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.counter_layout, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
Log.d("JJJ", "size is " + counterList.size)
return counterList.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindView(counterList[position + COUNTER_OFFSET].toString())
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
companion object {
const val COUNTER_MAX = 250
const val COUNTER_OFFSET = 1
}
}
I have placed logs as you can see and on the bindView function, the value passed is correct but never displayed on the actuial list ui widget.
Here is how i initiate the list widget with the adapter:
counterList.apply {
adapter = counterAdapter
// setHasFixedSize(true)
layoutManager = LinearLayoutManager(this#MainActivity)
}
Here is the counter layout of each item:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:id="#+id/counterText"
style="#style/TextAppearance.AppCompat.Headline" />
</androidx.constraintlayout.widget.ConstraintLayout>
Activty 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=".MainActivity">
......
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/counterList"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginTop="20dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#id/counterButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
Thanks in advance
I think you have the old view reference in the holder.
Try to get rid of it and use itemView instead
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bindView(counterValue: String) {
Log.d("JJJ", "counterValue is $counterValue")
itemView.counterText.text = counterValue
Log.d("JJJ", " view.counterText is ${itemView.counterText.text}")
}
}
I have a list of 5 elements in a recyclerview, set up like a to do list. There is a listener on the checkbox in each row, and for the purposes of this minimal reproducible example whenever you check any boxes it randomly sets the value of the 5 checkboxes. When an item is unchecked, it should appear in black text, and when an item is checked it should appear in gray text and italic.
When I check a box and reset the values, usually the UI updates as expected. However, sometimes one item sticks in the wrong layout so the checkbox shows the correct value but the text style is wrong. Why is this behavior inconsistent and how can I ensure the UI is refreshed every time?
Here's the entire MRE:
MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import com.dalydays.android.mre_recyclerview_refresh_last_item.databinding.ActivityMainBinding
import kotlin.random.Random
class MainActivity : AppCompatActivity() {
private lateinit var adapter: ToDoAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this
binding.itemsList.layoutManager = LinearLayoutManager(this)
val onCheckboxClickListener: (ToDoItem) -> Unit = { _ ->
adapter.submitList(getSampleList())
}
adapter = ToDoAdapter(onCheckboxClickListener)
binding.itemsList.adapter = adapter
adapter.submitList(getSampleList())
}
private fun getSampleList(): List<ToDoItem> {
val sampleList = mutableListOf<ToDoItem>()
sampleList.add(ToDoItem(id=1, description = "first item", completed = Random.nextBoolean()))
sampleList.add(ToDoItem(id=2, description = "second item", completed = Random.nextBoolean()))
sampleList.add(ToDoItem(id=3, description = "third item", completed = Random.nextBoolean()))
sampleList.add(ToDoItem(id=4, description = "fourth item", completed = Random.nextBoolean()))
sampleList.add(ToDoItem(id=5, description = "fifth item", completed = Random.nextBoolean()))
return sampleList
}
}
ToDoItem.kt
data class ToDoItem(
var id: Long? = null,
var description: String,
var completed: Boolean = false
)
ToDoAdapter.kt
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.dalydays.android.mre_recyclerview_refresh_last_item.databinding.ChecklistItemCheckedBinding
import com.dalydays.android.mre_recyclerview_refresh_last_item.databinding.ChecklistItemUncheckedBinding
const val ITEM_UNCHECKED = 0
const val ITEM_CHECKED = 1
class ToDoAdapter(private val onCheckboxClick: (ToDoItem) -> Unit): ListAdapter<ToDoItem, RecyclerView.ViewHolder>(ToDoItemDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_CHECKED -> ViewHolderChecked.from(parent)
else -> ViewHolderUnchecked.from(parent)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val toDoItem = getItem(position)
when (holder) {
is ViewHolderChecked -> {
holder.bind(toDoItem, onCheckboxClick)
}
is ViewHolderUnchecked -> {
holder.bind(toDoItem, onCheckboxClick)
}
}
}
override fun getItemViewType(position: Int): Int {
val toDoItem = getItem(position)
return when (toDoItem.completed) {
true -> ITEM_CHECKED
else -> ITEM_UNCHECKED
}
}
class ViewHolderChecked private constructor(private val binding: ChecklistItemCheckedBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(toDoItem: ToDoItem, onCheckboxClick: (ToDoItem) -> Unit) {
binding.todoItem = toDoItem
binding.checkboxCompleted.setOnClickListener {
onCheckboxClick(toDoItem)
}
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolderChecked {
val layoutInflater = LayoutInflater.from(parent.context)
return ViewHolderChecked(ChecklistItemCheckedBinding.inflate(layoutInflater, parent, false))
}
}
}
class ViewHolderUnchecked private constructor(private val binding: ChecklistItemUncheckedBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(toDoItem: ToDoItem, onCheckboxClick: (ToDoItem) -> Unit) {
binding.todoItem = toDoItem
binding.checkboxCompleted.setOnClickListener {
onCheckboxClick(toDoItem)
}
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolderUnchecked {
val layoutInflater = LayoutInflater.from(parent.context)
return ViewHolderUnchecked(ChecklistItemUncheckedBinding.inflate(layoutInflater, parent, false))
}
}
}
}
class ToDoItemDiffCallback : DiffUtil.ItemCallback<ToDoItem>() {
override fun areItemsTheSame(oldItem: ToDoItem, newItem: ToDoItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: ToDoItem, newItem: ToDoItem): Boolean {
return oldItem == newItem
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<RelativeLayout
android:id="#+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/items_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:scrollbars="none" />
</RelativeLayout>
</layout>
checklist_item_checked.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">
<data>
<variable
name="todoItem"
type="com.dalydays.android.mre_recyclerview_refresh_last_item.ToDoItem" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="#+id/checkbox_completed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:checked="#{todoItem.completed}"
android:textAppearance="?attr/textAppearanceListItem"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/tv_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:text="#{todoItem.description}"
android:textAppearance="?attr/textAppearanceListItem"
android:textColor="#65000000"
android:textStyle="italic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/checkbox_completed"
app:layout_constraintTop_toTopOf="parent"
tools:text="Mow the lawn" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
checklist_item_unchecked.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">
<data>
<variable
name="todoItem"
type="com.dalydays.android.mre_recyclerview_refresh_last_item.ToDoItem" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="#+id/checkbox_completed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:checked="#{todoItem.completed}"
android:textAppearance="?attr/textAppearanceListItem"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/tv_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:text="#{todoItem.description}"
android:textAppearance="?attr/textAppearanceListItem"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/checkbox_completed"
app:layout_constraintTop_toTopOf="parent"
tools:text="Mow the lawn" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Modify these methods
override fun areItemsTheSame(oldItem: ToDoItem, newItem: ToDoItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: ToDoItem, newItem: ToDoItem): Boolean {
return ((oldItem.id == newItem.id) && (oldItem.description == newItem.description) && (oldItem.completed == newItem.completed)
}
Also override the methods getNewListSize() & getOldListSize()
Maybe not the "best" solution, but this is what I came up with. I determined that there is probably an issue with the timing between the checkbox animation and the recyclerview refresh animation. Sometimes, depending on how long it takes the recyclerview to diff, the recyclerview can attempt to refresh before or after the checkbox finishes animating. When it finishes before, the checkbox animation blocks the recyclerview animation and leaves the UI in the wrong state. Otherwise it appears to work as intended.
I decided to manually run adapter.notifyItemChanged(position) even though RecyclerView.ListAdapter is supposed to handle this automatically. It still animates somewhat inconsistently depending on when the diff finishes calculating, but it's much better than leaving the UI in a bad state, and it's much better than refreshing the entire list every time with notifyDataSetChanged().
In MainActivity, change the checkboxlistener to this:
val onCheckboxClickListener: (ToDoItem, Int) -> Unit = { _, position ->
adapter.submitList(getSampleList())
adapter.notifyItemChanged(position)
}
In ToDoAdapter, change class header to this:
class ToDoAdapter(private val onCheckboxClick: (ToDoItem, Int) -> Unit): ListAdapter<ToDoItem, RecyclerView.ViewHolder>(ToDoItemDiffCallback()) {
And change both bind() functions to this:
fun bind(toDoItem: ToDoItem, onCheckboxClick: (ToDoItem, Int) -> Unit) {
binding.todoItem = toDoItem
binding.checkboxCompleted.setOnClickListener {
onCheckboxClick(toDoItem, layoutPosition)
}
binding.executePendingBindings()
}
I've been trying for a week loading items inside some horizontal recyclerviews contained in one vertical recyclerview(parent). Both need to have endless scrolling the horizontal ones to the right and the vertical while descending.
Right now the endless scrolling and the loading works. However, this is not working correctly because the horizontal ones are loading crazy data from the other children (My guess is that I'm creating the presenters inside the ViewHolder and some how data gets mixed up)
Is there any other way of doing this. That could work better and faster than my approach. Also, some this I can notice the vertical loading is not as smooth as I've seen in some other apps.
Any help would be greatly appreciated
class EventCatalogAdapter(private val presenter: EventCatalogPresenter):
RecyclerView.Adapter<EventCatalogAdapter.ViewHolder>() {
override fun getItemCount() = presenter.getCategoryEventCount()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
EventCatalogAdapter.ViewHolder(parent.inflate(R.layout.item_event_catalog), viewType)
override fun onBindViewHolder(holder: ViewHolder, position: Int) =
presenter.bind(holder, position)
override fun getItemViewType(position: Int) = presenter.getItemType(position)
class ViewHolder(itemView: View, type: Int): RecyclerView.ViewHolder(itemView),
EventCatalogItemView, EventViewList, OnItemClickListener<Event>,
EndlessScrollListener.DataLoader {
private lateinit var companyId: String
private lateinit var catId: String
private val eventPresenter = EventPresenterImpl(this)
override val titleFormat: String? get() = ""
init {
val layoutManager = HorizontalLinearLayoutManager(itemView.context,
LinearLayoutManager.HORIZONTAL, false)
itemView.rvEvents.layoutManager = layoutManager
itemView.rvEvents.addItemDecoration(HorizontalItemDecorator(itemView.context))
itemView.rvEvents.adapter = EventAdapter(eventPresenter, type, this)
itemView.rvEvents.addOnScrollListener(EndlessScrollListener(layoutManager, this))
}
override fun setTitleVisibility(b: Boolean) {
itemView.tvCategoryTitle.visibility = if(!b) View.GONE else View.VISIBLE
}
override fun setCategoryTitle(title: String) {
itemView.tvCategoryTitle.text = title
}
override fun eventsByCategory(companyId: String, categoryId: String) {
this.companyId = companyId
catId = categoryId
eventPresenter.getEventsByCategories(companyId, categoryId)
}
override fun loadMoreData(totalItems: Int) {
Log.d("LOAD", "Event presenter: $eventPresenter for category: $catId")
eventPresenter.getMoreEventsByCategories(companyId, catId, totalItems)
}
override fun setActiveEvent(event: Event?) {}
override fun showSettings() {}
override fun sendMessage(action: String, bundle: Bundle?) {}
override fun showStatus(status: Int) {}
override fun refresh() {
itemView.rvEvents.adapter.notifyDataSetChanged()
}
override fun showMessageTemplate(code: Int) {}
override fun hideMessageTemplate() {
refresh()
}
override fun onItemClick(item: Event) {
}
}
}
class EventAdapter(private val presenter: EventPresenter,
private val listener: OnItemClickListener<Event>):
RecyclerView.Adapter<EventAdapter.ViewHolderItemView>() {
private var type: Int = EVENT_STANDARD
constructor(presenter: EventPresenter, type: Int, listener: OnItemClickListener<Event>) :
this(presenter, listener) {
this.type = type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
ViewHolderItemView(parent.inflate(type))
override fun onBindViewHolder(holder: ViewHolderItemView, position: Int) =
presenter.bind(holder, position)
override fun getItemCount() = presenter.getCount()
class ViewHolderItemView(itemView: View) : RecyclerView.ViewHolder(itemView), EventItemView {
override fun setEventDate(date: Date?) {
itemView.tvDate.text = Tools.formatDate(itemView.context, date)
}
override fun setName(name: String) {
itemView.tvName.text = name
}
override fun setScannerVisibility(scannerVisibility: Boolean) {
itemView.ibScanner.visibility = if (scannerVisibility) View.VISIBLE else View.INVISIBLE
}
override fun setEventPoster(posterUrl: String, transformation: Transformation?) {
Tools.loadImage(posterUrl, itemView.ivPoster, transformation, R.mipmap.portrait_test)
}
override fun setTotalRegistrants(totalRegistrants: Long) {
itemView.tvQtyRegs.text = totalRegistrants.toString()
}
override fun addScanAction(event: Event) {
itemView.ibScanner.setOnClickListener {
val auth = FirebaseAuth.getInstance()
val prefs = PreferenceHelper.customPrefs(itemView.context, auth.currentUser!!.uid)
prefs.edit().putString(EventInteractorImpl.FIELD_EVENT_ID, event.eventId).apply()
(itemView.context as SettingsActivity).launchScanner()
}
}
override fun addEventAction(event: Event) {
itemView.setOnClickListener {
(itemView.context as SettingsActivity).showRegistrants(event)
}
}
}
}
// Parent items:
<android.support.constraint.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="220dp">
<TextView
android:id="#+id/tvCategoryTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="#dimen/vertical_spacing"
android:paddingBottom="#dimen/vertical_spacing"
android:layout_marginEnd="#dimen/horizontal_spacing"
android:layout_marginStart="#dimen/horizontal_spacing"
android:textAlignment="center"
android:textSize="18sp"
android:textAllCaps="true"
android:fontFamily="sans-serif-light"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="#string/txt_cat_title" />
<android.support.v7.widget.RecyclerView
android:id="#+id/rvEvents"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="#dimen/vertical_spacing"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvCategoryTitle" />
</android.support.constraint.ConstraintLayout>
// Children items
<android.support.constraint.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="110dp"
android:layout_height="match_parent"
android:stateListAnimator="#animator/tile_elevation">
<ImageView
android:id="#+id/ivPoster"
android:layout_width="0dp"
android:layout_height="0dp"
android:contentDescription="#string/txt_event_image"
android:src="#mipmap/portrait_test"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/tvName"
style="#style/SubTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textIsSelectable="false"
android:textAllCaps="true"
app:layout_constraintBottom_toTopOf="#+id/ibRegistrants"
app:layout_constraintStart_toStartOf="#+id/ibRegistrants"
tools:text="Washington D.C. " />
<TextView
android:id="#+id/tvDate"
style="#style/SubTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="false"
app:layout_constraintBottom_toTopOf="#+id/tvName"
app:layout_constraintStart_toStartOf="#+id/ibRegistrants"
tools:text="03/23" />
<TextView
android:id="#+id/tvQtyRegs"
style="#style/Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textIsSelectable="false"
app:layout_constraintBottom_toBottomOf="#+id/ibRegistrants"
app:layout_constraintStart_toEndOf="#+id/ibRegistrants"
app:layout_constraintTop_toTopOf="#+id/ibRegistrants"
tools:text="140" />
<ImageView
android:id="#+id/ibRegistrants"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginStart="8dp"
android:contentDescription="#string/txt_registrants"
android:src="#drawable/ic_registrants_48px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="#+id/ibScanner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="#string/txt_registrants"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_scan_action" />
</android.support.constraint.ConstraintLayout>