So here is my issue:
- I am fetching data from an api (successfully)
- I am displaying this data in a recyclerview (successfully)
- I am filtering the recyclerview using a searchview (sucessfully)
- I have a click listener on the items within the recyclerview that is starting a new activity with the item[position] informations and this work perfectly before i filter my recycler view. When I do so, filter, the positions of my items are getting messed up so the informations I am giving to the new activity are the wrong ones... I thought at first it was because I used List instead of ArrayList (that are mutable) but the issue doesn't seem to come from there neither... Truth is I am stuck, I am learning android and kotlin and this is how far I can go without help :
(Items that I display are houses)
HouseAdapter (recyclerview adapter) :
class HouseAdapter(private var recyclerViewInterface: RecyclerViewInterface) : RecyclerView.Adapter<HouseAdapter.HouseViewHolder>() {
inner class HouseViewHolder(val binding: ItemHouseBinding) : RecyclerView.ViewHolder(binding.root){
private val formatter : NumberFormat = DecimalFormat("$#,###")
fun bind(house: House) {
binding.tvPrice.text = formatter.format(house.price)
"${house.zip} ${house.city}".also { binding.tvAddress.text = it }
binding.tvBed.text = house.bedrooms.toString()
binding.tvBath.text = house.bathrooms.toString()
binding.tvLayers.text = house.size.toString()
val houseImageUrl : String = BASE_URL house.image
Glide.with(binding.root.context).load(houseImageUrl).centerCrop().into(binding.ivHouse)
}
}
private val diffCallBack = object : DiffUtil.ItemCallback<House>(){
// Call to check whether two items represent the same item
override fun areItemsTheSame(oldItem: House, newItem: House): Boolean {
return oldItem.id == newItem.id
}
// Call to check whether two items have the same data
override fun areContentsTheSame(oldItem: House, newItem: House): Boolean {
return oldItem == newItem
}
}
private val differ = AsyncListDiffer(this, diffCallBack)
var houses: List<House>
get() = differ.currentList
set(value) {differ.submitList(value)}
override fun getItemCount(): Int {
return houses.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HouseViewHolder {
return HouseViewHolder(ItemHouseBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
))
}
override fun onBindViewHolder(holder: HouseViewHolder, position: Int) {
println("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO : $position")
holder.bind(houses[position])
holder.binding.vBody.setOnClickListener {
recyclerViewInterface.onItemClick(position)
}
}
fun setFilteredList(filteredList: List<House>){
this.houses = filteredList
}
}
HomeFragment (containing the recycler view) :
class HomeFragment : Fragment(), RecyclerViewInterface {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private lateinit var container : ViewGroup
private lateinit var houseAdapter: HouseAdapter
private lateinit var listHouses: ArrayList<House>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
if (container != null) {
this.container = container
}
setupRecyclerView()
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.getHouses(API_KEY)
} catch (e: IOException) {
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException){
Log.e(TAG, "HttpException, unexpected response ")
return@launchWhenCreated
}
if (response.isSuccessful && response.body() != null){
listHouses = (response.body() as ArrayList<House>?)!!
houseAdapter.houses = listHouses
listHouses.sortWith(compareBy {it.price})
listHouses.forEach {println(it.price) }
}else{
Log.e(TAG, "Response not successful")
}
}
setupSearchView()
return binding.root
}
// Called on fragment launch - Add OnQueryTextListener to the SearchView
private fun setupSearchView(){
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener,
androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String): Boolean {
filterList(newText)
return true
}
})
}
// Called when text from SearchView is changed - Create new filtered list or display empty information
private fun filterList(text: String) {
val filteredList = arrayListOf<House>()
listHouses.forEach { house : House ->
if (house.city.contains(text) || house.zip.contains(text)){
filteredList.add(house)
}
}
if (filteredList.isEmpty()){
binding.rvHouses.visibility = View.INVISIBLE
binding.ivSearchEmpty.visibility = View.VISIBLE
binding.tvSearchEmpty1.visibility = View.VISIBLE
binding.tvSearchEmpty2.visibility = View.VISIBLE
}else{
houseAdapter.setFilteredList(filteredList)
binding.rvHouses.visibility = View.VISIBLE
binding.ivSearchEmpty.visibility = View.INVISIBLE
binding.tvSearchEmpty1.visibility = View.INVISIBLE
binding.tvSearchEmpty2.visibility = View.INVISIBLE
}
}
// Called on fragment launch - Setup the RecyclerView with the HouseAdapter
private fun setupRecyclerView() = binding.rvHouses.apply {
houseAdapter = HouseAdapter(this@HomeFragment)
adapter = houseAdapter
layoutManager = LinearLayoutManager(context)
}
// Called when user click on House item - Pass properties from the chosen house to DetailActivity
override fun onItemClick(position: Int) {
val i = Intent(this.context, DetailActivity::class.java)
println("OOOOOOOOOOOOOOOOOOOOOOOOOOOOO : $position")
i.putExtra("PRICE", listHouses[position].price)
i.putExtra("IMAGE_URL", listHouses[position].image)
i.putExtra("BEDS", listHouses[position].bedrooms)
i.putExtra("BATH", listHouses[position].bathrooms)
i.putExtra("SIZE", listHouses[position].size)
i.putExtra("LAT", listHouses[position].latitude)
i.putExtra("LONG", listHouses[position].longitude)
i.putExtra("DESC", listHouses[position].description)
startActivity(i)
}
}
(I used the println("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO : $position")
to keep track of the position of the items at the bindviewholder function of the recylcerview and when i click on an item to see if it was corresponding... The answer is : "it depends" sometimes yes sometimes no. I tried to find some logic to it but i am unsure of my theory : I think that when i filter the recyclerview without seeing all the items before doing so, the filtered list does not know what position give to the items that were "unseen".
If you have any other advice besides my issue I'd be happy to hear it (As I said I am currently learning kotlin)
CodePudding user response:
Check out positions in RecyclerView.
There are two kinds, layout and adapter. You want to use the adapter one. So change your onBindViewHolder
function to use getAbsoluteAdapterPosition
:
holder.binding.vBody.setOnClickListener {
recyclerViewInterface.onItemClick(holder.absoluteAdapterPosition) // not position
}