RecyclerView kotlin don't remember selected items, after add next items - android

Hello i have problem with my shopping List. I have RoomDatabase and recycler view to adding and reading my list. I create action when user click on button row change background, but after i add new items then don't see selected items. Where is a problem?
My Adapter:
lass ListAdapter() : RecyclerView.Adapter<ListAdapter.MyViewHolder>() {
private var itemList = emptyList<Items>()
lateinit var viewModel: ItemsViewModel
inner class MyViewHolder(itemview: View) : RecyclerView.ViewHolder(itemview) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.items_row, parent, false)
)
}
override fun getItemCount(): Int {
return itemList.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = itemList[position]
holder.itemView.name_rec.text = currentItem.itemsName
holder.itemView.amount_rec.text = currentItem.amountItems.toString()
holder.itemView.rowLayout.setOnClickListener {
val action = ListFragmentDirections.actionListFragmentToUpdateFragment(currentItem)
holder.itemView.findNavController().navigate(action)
}
holder.itemView.btnDeleteee.setOnClickListener {
val action = ListFragmentDirections.actionListFragmentToDeleteFragment(currentItem)
holder.itemView.findNavController().navigate(action)
}
holder.itemView.btncheckbox.setOnClickListener {
holder.itemView.rowLayout.setBackgroundColor(Color.parseColor("#FFBA5F"))
}
}
fun setData(items: List<Items>) {
this.itemList = items
notifyDataSetChanged()
}
My ListFragment:
class ListFragment : Fragment() {
lateinit var itemsViewModel: ItemsViewModel
private val args by navArgs<UpdateFragmentArgs>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_list, container, false)
//menu
setHasOptionsMenu(true)
//RecyclerView
val recyclerView = view.recycler_view_list
val adapter = ListAdapter()
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
//ViewWModel
itemsViewModel = ViewModelProvider(this).get(ItemsViewModel::class.java)
itemsViewModel.readAllData.observe(viewLifecycleOwner, Observer { items ->
adapter.setData(items)
})
view.floatingActionButton2.setOnClickListener {
findNavController().navigate(R.id.action_listFragment_to_addFragment)
}
Where is problem with save all actions who do user in list?
Thanks for help

You have to store the selected item and in onBindViewHolder you have to check that the item for this position is previously selected item or not. If this is the selected item then change the background or make if default.
Actually the problem is as you are changing the background in onClickListener so when you are updating the itemList with setData(items: List<Items>) method RecyclerView reload all the visible items and your previously selected item loses its background

Related

"Empty list doesn't contain element at index 0" error when implementing Android Recycler View with checkbox

I am implementing a recycler view with it's items as checkbox. My data is coming from ROOM database and this recycler view is inside a dialog fragment.
Dialog Fragment :
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ScheduleFloorDialogBinding.inflate(layoutInflater)
createProfileViewModel = CreateProfileViewModel(Application())
floorProfileDialogAdapter = FloorProfileDialogAdapter()
binding.rvFloorsForScheduling.layoutManager = LinearLayoutManager(requireActivity())
binding.rvFloorsForScheduling.adapter = floorProfileDialogAdapter
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val floorList: MutableList<String> = mutableListOf()
//Getting list of all floors
createProfileViewModel.totalFloors.observe(viewLifecycleOwner) {
Timber.d("List of floors received : $it")
val intList = it.map(String::toInt)
val maxFloorValue = intList.last()
var count = 0
try {
while (count <= maxFloorValue) {
floorList.add(count.toString())
count++
}
} catch (e: Exception) {
Timber.d("Exception: $e")
}
floorProfileDialogAdapter.getAllFloors(floorList)
Timber.d("Floor List : $floorList")
}
}
I am able to send list of data from here to my adapter.
Adapter:
class FloorProfileDialogAdapter() : RecyclerView.Adapter<FloorProfileDialogAdapter.MyViewHolder>() {
var floors = emptyList<String>()
inner class MyViewHolder(val binding: ScheduleFloorDialogItemBinding) :
RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ScheduleFloorDialogItemBinding.inflate(inflater, parent, false)
return MyViewHolder(binding)
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentFloor = floors[position]
Timber.d("Current floor: $currentFloor")
holder.binding.floorCheckBox.text = "Floor $currentFloor"
}
override fun getItemCount(): Int {
return floors.toString().length
}
fun getAllFloors(floorsReceived: List<String>) {
Timber.d("Floors received : $floorsReceived")
this.floors = floorsReceived
}
}
Log inside the Adapter's getAllFloor method shows that list has been received:
But inside onBindViewHolder() when I use the position I get the error saying :
java.lang.IndexOutOfBoundsException: Empty list doesn't contain element at index 0.
The view is already inflated when you initialized the floorProfileDialogAdapter with an empty list, it won't be changed till you use notifyDataSetChange()
it's a solution which is not recommended
or
Using ListAdapter from Androidx Recycler view Package: it has its own submit list so every time you submit a list it notifies data set change and it compares it with the previous one
Check documentation

How do I implement onClickListener to a recyclerView to open a new fragment when clicked

I am trying to implement onClickListener with recyclerView to open a new fragement, I understand that they are a lot this on stackoverflow already but as a beginner I find them somewhat complex.
Like I said I want to open a fragment when I click on recyclerView, and I have some questions;
Mainly, how do I do this??
how do I controll the Views (i.e textViews, imageViews, etc) of the screens I'm navigating to??
Here's the fragment that has the recyclerView
class NewScreenFragment : Fragment() {
private var _binding: FragmentNewScreenBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
// for the recyclerView
private lateinit var newArrayList: ArrayList<DataClass>
// variables to the recyclerView views
lateinit var imageId: Array<Int>
lateinit var title: Array<String>
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentNewScreenBinding.inflate(inflater, container, false)
val root: View = binding.root
imageId = arrayOf(
R.drawable.ways_start_cpa_marketing,
R.drawable.ways_start_gigging,
R.drawable.ways_teach_english_online,
R.drawable.ways_test_websites,
R.drawable.ways_tutor_on_your_own_time,
R.drawable.ways_use_your_voice,
R.drawable.ways_write_a_list
)
title = arrayOf(
getString(R.string.Become_A_Dog_Walker),
getString(R.string.Become_A_Proofreader),
getString(R.string.Become_A_Virtual_Assistant),
getString(R.string.Make_Things_to_Sell),
getString(R.string.Give_your_opinion),
getString(R.string.Play_Games),
getString(R.string.Start_A_Blog),
)
// this creates a vertical layout Manager
binding.recyclerview.layoutManager = LinearLayoutManager(context)
/** I change LinearLayoutManager(this)
* to LinearLayoutManager(context)
*
* remember this in case of any issue
**/
binding.recyclerview.setHasFixedSize(true)
newArrayList = arrayListOf<DataClass>()
getUserData()
return root
}
private fun getUserData() {
for (i in imageId.indices){
val dataClass = DataClass(imageId[i],title[i])
newArrayList.add(dataClass)
}
// ArrayList of class ItemsViewModel
// val data = ArrayList<DataClass>()
// This loop will create Views containing
// the image with the count of view
// This will pass the ArrayList to our Adapter
// val adapter = RecyclerAdapter(data)
// Setting the Adapter with the recyclerview
binding.recyclerview.adapter = RecyclerAdapter(newArrayList)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Here's the Adapter.kt
class RecyclerAdapter(private val mList: ArrayList<DataClass>) : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
// create new views
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// inflates the card_view_design view
// that is used to hold list item
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.new_screen_recyclerview_look, parent, false)
return ViewHolder(view)
}
// binds the list items to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val dataClass = mList[position]
// sets the image to the imageview from our itemHolder class
holder.imageView.setImageResource(dataClass.cardImage)
// sets the text to the textview from our itemHolder class
holder.textView.text = dataClass.cardTitle
}
// return the number of the items in the list
override fun getItemCount(): Int {
return mList.size
}
// Holds the views for adding it to image and text
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.findViewById(R.id.card_image)
val textView: TextView = itemView.findViewById(R.id.card_title)
// implement the onClickListener event here
init {
itemView.setOnClickListener {
}
}
}
}
Not needed though, but for more clarify.. Here's my Data class;
data class DataClass(var cardImage: Int, var cardTitle: String) {
}
I really appreciate your help, thanks to you in advance..
Create a new parameter on your Adapter constructor
Change
class RecyclerAdapter(private val mList: ArrayList<DataClass>)
to
class RecyclerAdapter(private val mList: ArrayList<DataClass>, private val onItemClicked: (DataClass) -> Unit)
Set the clickListener for you item inside onBindViewHolder. If you wanna make the whole item clickable, set:
holder.root.setOnClickListener{
onItemClicked(dataClass)
}
On your fragment, when creating the adapter, pass the new parameter
binding.recyclerview.adapter = RecyclerAdapter(newArrayList){ dataClass ->
//Here, 'dataClass' will be the clicked item on recyclerView. Here you can do any logic that's supposed to happen after the click, like opening a new fragment passing 'dataClass' as parameter.
}
To open a new frag passing dataClass as parameter, check "newInstance" pattern.
Understanding the Fragment.newInstance method

How to display items in RecyclerView on fragment with data from Firestore using firestoreui

I'm having a problem with making a Firestoreui show data from Firestore in RecycleView in a fragment. The app is supposed to show images in a RecycleView after I clicked on an ImageView to change from one RecycleView fragment to another RecycleView fragment but it does not show anything.
I know there are plenty of questions like this but I don't seem to find the correct solution to this problem.
Here's my fragment code
class PhotoGalleryFragment : Fragment() {
private var db: FirebaseFirestore=FirebaseFirestore.getInstance()
lateinit var binding: FragmentPhotoGalleryBinding
lateinit var adapter: GalleryAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_photo_gallery, container, false
)
//set-up recyclerview
setUpRecycler()
return binding.root
}
private fun setUpRecycler() {
val query = db.collection("photos")
val options = FirestoreRecyclerOptions.Builder<Photos>()
.setQuery(query, Photos::class.java)
.build()
adapter = GalleryAdapter(options)
binding.galleryRecyclerView.itemAnimator = DefaultItemAnimator()
binding.galleryRecyclerView.layoutManager = GridLayoutManager(activity, 2)
binding.galleryRecyclerView.adapter = adapter
}
override fun onStart() {
super.onStart()
adapter.startListening()
}
override fun onStop() {
super.onStop()
adapter.stopListening()
}
}
}
Here's my adapter class
class GalleryAdapter(options: FirestoreRecyclerOptions<Photos>) :
FirestoreRecyclerAdapter<Photos, GalleryAdapter.GalleryViewHolder>(options) {
class GalleryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var photo: ImageView = itemView.findViewById(R.id.photo_image)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GalleryViewHolder {
val itemView =
LayoutInflater.from(parent.context)
.inflate(R.layout.item_gallery, parent, false)
return GalleryViewHolder(itemView)
}
/**
* #param model the model object containing the data that should be used to populate the view.
* #see .onBindViewHolder
*/
override fun onBindViewHolder(holder: GalleryViewHolder, position: Int, model: Photos) {
Glide.with(holder.photo.context)
.load(model.imageUrl)
.placeholder(R.drawable.ic_image)
.error(R.drawable.ic_star)
.into(holder.photo)
//set up navigation
holder.itemView.setOnClickListener { view: View ->
view.findNavController().navigate(R.id.action_photoGalleryFragment_to_photoFragment)
}
}
}
Here's my model class
class Photos {
var imageUrl: String? = null
constructor(imageUrl: String?) {
this.imageUrl = imageUrl
}
constructor() {}
}
This the data structure from firestore

RecyclerView Button-only item onClick

So I'm doing RecyclerView of buttons. And I don't know how/where to write the onClick for the button. I have a JSONArray to pass, and the text of the button will have the cName.
So this is my item_unit.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="#+id/btn_unit"
android:background="#000000"
android:textColor="#ffffff"
android:layout_margin="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
This is my ItemAdapter
//Here the stype is <CategoryItem> because this button will open
//new fragment to show the list of the items.
//So it's recyclerview item opens another recyclerview.
class CatAdapter(var btnList: ArrayList<CategoryItem>, var context: Context): RecyclerView.Adapter<ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
var v: View = LayoutInflater.from(context).inflate(R.layout.item_unit, parent, false)
return ViewHolder(v)
}
override fun getItemCount( ): Int {
return btnList.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val data = btnList[position]
holder.itemBtn.text = data.name
}
}
class ViewHolder(view: View): RecyclerView.ViewHolder(view){
var itemBtn = view.findViewById<Button>(R.id.item_btn)
}
This recycler view is in a fragment. I need to
class MainActFrag : Fragment() {
var btnList = ArrayList<CategoryItem>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
var view = inflater.inflate(R.layout.fragment_main_act, container, false)
//Recycler view
view.recyclerView.layoutManager = LinearLayoutManager(context)
//I have the url
var stringRequest = StringRequest(Request.Method.GET, url,
Response.Listener {
var jsonObject = JSONObject(it)
var jsonArrayCates =jsonObject.getJSONArray("category")
for(i in 0 until jsonArrayCats.length()){
var btns =jsonArrayCates.getJSONObject(i)
var iD = btns.getString("id")
var name = btns.getString("name")
var des = btns.getString("description")
btnList.add(CategoryItem(cID, cName, cDes))
}
val adapter = ItemAdapter(btnList, view.context)
recyclerView.adapter = adapter
},
Response.ErrorListener {}
)
Volley.newRequestQueue(view.context).add(stringRequest)
return view
}
}
So I don't know where to call each item onClick. If it's just a normal TextView item, I can do do it, but since I chose to make the item button, I don't know the specific way to handle this. I thought it would be easy because the button is... clickable by nature?
Thank you!
add a bind function to the ViewHolder Class then you can put your click handler on each button
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val data = btnList[position]
holder.itemBtn.text = data.name
holder.bindButtons()
}
class ViewHolder(view: View): RecyclerView.ViewHolder(view){
var itemBtn = view.findViewById<Button>(R.id.item_btn)
fun bindButtons()
itemBtn.setOnClickListener{
//do you click event stuff
}
}

RecyclerView Get view at particular position and retrieve data from it android kotlin

i have recyclerview item which has 4 edittexts. Also i have add new item button which add new item and new edittexts(they need to be populated from the use) i`m trying to retrieve all data from the fields when user click save button. Here is my code:
class SectionsRecyclerAdapter(private val educationList: MutableList<Any>) :
RecyclerView.Adapter<SectionsRecyclerAdapter.ViewHolder>() {
class ViewHolder(val item: View) : RecyclerView.ViewHolder(item)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_new_section, parent, false)
return ViewHolder(view)
}
override fun getItemCount() = educationList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (holder != null) {
holder.item.deleteBtn.visibility = if (position != 0) View.VISIBLE else View.GONE
holder.item.deleteBtn.setOnClickListener {
educationList.removeAt(position)
notifyItemRemoved(position)
notifyItemRangeRemoved(position, educationList.size)
}
}
}
fun addItem() {
educationList.add(EducationModel())
notifyItemInserted(educationList.size)
}
}
Education Fragment :
class EducationFragment : Fragment(), ValidationInterface {
private var educationList: MutableList<Any> = mutableListOf()
private lateinit var sectionAdapter : SectionsRecyclerAdapter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_education, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
educationList.add(EducationModel())
sectionAdapter = SectionsRecyclerAdapter(educationList)
educationRv.apply {
layoutManager = LinearLayoutManager(activity)
adapter = ScaleInAnimationAdapter(sectionAdapter)
itemAnimator = LandingAnimator()
}
addSectionBtn.setOnClickListener {
sectionAdapter.addItem()
educationRv.smoothScrollToPosition(educationList.size)
}
}
companion object {
val instance = EducationFragment()
}
override fun validateAndSave(): Boolean {
//Here i want to get data from every child and then parse it to my model EducationModel
val model = educationRv.getChildAt(0)
list.add(model)
CreateResumeActivity.modelInstance.educationList.addAll(list)
return true
}
Of course if there are better solution i will be glad, but for now i`m stuck with this... My main goal is when user click save i need to retrieve value from the edittext ( University, StartDate, EndDate, Summary) and put it in EducationModels
Inside a recycler view each view holder can recycle his views. For that reason you have to keep all edittext values in a public list inside your adapter and access it from component which hold that adapter. Here is an proof of concept:
class MyAdapter: RecyclerView.Adapter(private val educationList: MutableList<Any>) {
val holderList = educationList.map { EditTextValues(...) };
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
//fill edit text just in case that it is recycled
holder.editText.text = holderList[position].editTextValue
holder.editText.onTextChangeLister { holderList[position] = EditTextValues(holder.editText.text) }
}
}
class MyAndroidComponent : Fragment {
val adapter: MyAdapter = ...
fun onCreate() {
saveButton.setOnClickListener {
// you can access your edit texts values here
adapter.holderList
}
}
}

Categories

Resources