Kotlin nested Recyclerview - kotlin

I really appropriate it if somebody could help me out.
I apply my second RecyclerView with a custom swipe Button object, here fun handleSwipeClick is set to handle the action.
My question is: how can i make this function (handleSwipeClick) to handle each row specifically?? Like Delete this row item
Adapter #1
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = baxters[position]
holder.listItem_time.text = item.intakeTime
holder.itemView.dose_recycler_view.apply {
dose_recycler_view.setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
dose_recycler_view.layoutManager = layoutManager
//Swipe action
val swipe = object: SwipeHelper(context,dose_recycler_view, 400){
override fun instaniateSwipeButton(
viewHolder: RecyclerView.ViewHolder,
buffer: MutableList<SwipeButton>
) {
// Adding Buttons
buffer.add(
SwipeButton(context,
"",
30,
R.drawable.ic_check_circle,
Color.parseColor("#66ff66"),
object : ButtonClickListener {
override fun handleSwipeClick(id: Int) {
// Click action
// TODO call to change LAST TAKEN and NEW INTAKE
Companion.errorToast(
context,
"Medicijn ingenomen. $id"
)
}
})
)
}
}
adapter = ClientDoseListAdapter(item.doses.toMutableList())
setRecycledViewPool(viewPool)
}
}
Adapter #2
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = doses[position]
/// DELETE THIS ROW
holder.med_name.text = item.medicineItem.name
holder.dose_amount.text = item.amount.toString()
}

First, create a delete method in ClientDoseListAdapter adapter, then call this method in the click action like,
fun delete(dose: Dose) {
val index: Int = doseList.indexOf(dose)
doseList.removeAt(index)
notifyDataSetChanged()
}
override fun handleSwipeClick(id: Int) {
// Click action
adapter.delete(adapter.getItem(viewHolder.adapterPosition))
}

Adapetr #1 looks like this:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = baxters[position]
holder.listItem_time.text = item.intakeTime
holder.itemView.dose_recycler_view.apply {
dose_recycler_view.setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
dose_recycler_view.layoutManager = layoutManager
// First init a new adapter
dosesAdapter = ClientDoseListAdapter(item.doses.toMutableList())
// Swipe action
val swipe = object: SwipeHelper(context, dose_recycler_view, 400){
override fun instaniateSwipeButton(
viewHolder: RecyclerView.ViewHolder,
buffer: MutableList<SwipeButton>
) {
// Adding Buttons
buffer.add(
SwipeButton(context,
"",
30,
R.drawable.ic_check_circle,
Color.parseColor("#66ff66"),
object : ButtonClickListener {
override fun confirmDoseTaken(id: Int) {
// Click action
// TODO call to change LAST TAKEN and NEW INTAKE
Companion.errorToast(
context,
"Medicijn ingenomen. $id"
)
// i can call removeItem method in adapter
dosesAdapter.removeItem()
}
})
)
}
}
// set recyclerView adapter
adapter = dosesAdapter
setRecycledViewPool(viewPool)
}
}

Related

how to call supportFragmentManager for dialogFragment in onBindViewHolder?

i have a problem here, idk how to show dialogFragment in my Adapter. i want to everytime my itemView click it will show my dialogFragment
this is my onBindViewHolder
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val type = list[position]
holder.binding.tvTitle.text = type.title
holder.binding.tvIsiNotes.text = type.content
holder.itemView.setOnClickListener {
val dialogFragment = AddFragment()
dialogFragment.show(supportFragmentManager, null)
}
if(position == list.size - 1){
onLoad?.let {
it()
}
}
}
this is my code using navigationcomponent, but i want to show the Dialog.
holder.itemView.setOnClickListener {
val action = HomeFragmentDirections.actionHomeFragmentToEditFragment(type)
holder.itemView.findNavController().navigate(action)
}
You could define a custom click listener class to pass as a parameter of your adapter:
class YourAdapter(
private val clickListener: (type: Int) -> Unit
) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val type = list[position]
...
holder.itemView.setOnClickListener(clickListener(type))
...
}
}
Then, instantiate your adapter as:
val adapter = YourAdapter { it ->
findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToEditFragment(it))
}

Deleting an item from RecyclerView causes shuffling or duplicating other items

I have a recyclerView that users can add or delete item rows and that rows saving the Firebase Firestore. Adding function works fine but deleting function does not. I created an interface (Listener) through PairDetailRecyclerAdapter. It contains DeleteOnClick method which have myList and position parameters. Also I have a deleteData method through my viewModel for deleting documents from Firestore. When i clicked the delete button on Firebase side everything is OK but on recyclerView side items duplicating themselves or shuffling
Here is the codes:
Interface and onClickListener from PairDetailRecyclerAdapter :
interface Listener {
fun DeleteOnClick(list: ArrayList<AnalyzeDTO>, position: Int)
}
override fun onBindViewHolder(holder: AnalyzeViewHolder, position: Int) {
holder.itemView.rrRatioText.text = "RR Ratio: ${list[position].rrRatio}"
holder.itemView.resultText.text = "Result: ${list[position].result}"
holder.itemView.causeForEntryText.text = "Reason: ${list[position].reason}"
holder.itemView.conceptText2.text = "Concept: ${list[position].concept}"
if (list[position].tradingViewUrl != null && list[position].tradingViewUrl!!.isNotEmpty()) {
Picasso.get().load(list[position].tradingViewUrl)
.into(holder.itemView.tradingviewImage);
}
holder.itemView.imageView.setOnClickListener {
listener.DeleteOnClick(list, holder.layoutPosition)
}
deleteData from ViewModel :
fun deleteData(position: Int) {
var chosenPair = ozelSharedPreferences.clickiAl().toString()
val currentU = Firebase.auth.currentUser
val dbCollection = currentU?.let {
it.email.toString()
}
database.collection(dbCollection!!).document("Specified").collection("Pairs")
.document(chosenPair).collection("Analysis").get().addOnSuccessListener { result ->
val newList = ArrayList<String>()
if (result != null) {
for (document in result) {
newList.add(document.id)
database.collection(dbCollection!!).document("Specified").collection("Pairs")
.document(chosenPair).collection("Analysis").document(newList[position]).delete()
}
}
}
}
Overrided Listener in PairDetailActivity
override fun DeleteOnClick(list: ArrayList<AnalyzeDTO>, position: Int) {
viewModel.deleteData(position)
list.removeAt(position)
recyclerA.notifyItemRemoved(position)
}
#SuppressLint("NotifyDataSetChanged")
fun deleteItem(i: Int, context: Context) {
question = dataList[i] as Questionio //this my model
dataList.removeAt(i)
notifyDataSetChanged()
}
//
this code works for me
in fact, it would be better if you did it as a model and it would be suitable for mvvm architecture.

Struggling to access Spinner outside of my recycler view

I have tried two different ways to access my spinner. Without success thus far.
I want to load the data for each driver as chosen.
To give an idea of my app.
Code for adapter:
class TableViewAdapter(var tripsheetlist: Tripsheetlist) : RecyclerView.Adapter<TableViewAdapter.RowViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RowViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.table_list_item, parent, false)
return RowViewHolder(itemView) }
override fun getItemCount(): Int { return tripsheetlist.videos.size + 1 // one more to add header row
}
override fun onBindViewHolder(holder: RowViewHolder, position: Int) {
val rowPos = holder.adapterPosition
if (rowPos == 0) {
// Header Cells. Main Headings appear here
holder.itemView.apply {
setHeaderBg(txtWOrder)
setHeaderBg(txtDElNote)
setHeaderBg(txtCompany)
// setHeaderBg(txtAddress)
setHeaderBg(txtWeight)
setHeaderBg(txtbutton1)
setHeaderBg(txtbutton2)
setHeaderBg(txttvdone)
txtWOrder.text = "WOrder"
txtDElNote.text = "DElNote"
txtCompany.text = "Company"
// txtAddress.text = "Address"
txtWeight.text = "Weight"
txtbutton1.text = "Delivered"
txtbutton2.text = "Exception"
txttvdone.text = ""
}
} else {
val modal = tripsheetlist.videos[rowPos -1]
holder.itemView.apply {
setContentBg(txtWOrder)
setContentBg(txtDElNote)
setContentBg(txtCompany)
setContentBg(txtWeight)
setContentBg(txtbutton1)
setContentBg(txtbutton2)
setContentBg(txttvdone)
val list : MutableList<String> = ArrayList()
list.add("Deon")
list.add("Leon")
list.add("David")
list.add("Dick")
println(list)
val spinner : Spinner = findViewById(R.id.spnDriver)
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
val item :String = list[p2]
if (item == "David")
{
txtWOrder.text = modal.WOrder.toString()
txtDElNote.text = modal.DElNote.toString()
txtCompany.text = modal.name.toString()
txtWeight.text = modal.id.toString()
}
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
I did it like this as a test for now. As I will get the drivers from my JSON. I don't have access to it yet so that is why the static values.
The problem I am getting now is: findViewById(R.id.spnDriver) must not be null
I first had my spinner class in my main activity and passed it over like this:
val list : MutableList<String> = ArrayList()
list.add("Deon")
list.add("Leon")
list.add("David")
list.add("Dick")
list.add("Jim")
list.add("Harry")
val adapter = ArrayAdapter( this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, list)
val spinner: Spinner = findViewById(R.id.spnDriver)
spinner.adapter = adapter
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
val item :String = list[p2]
Toast.makeText(this#MainActivity, "Driver $item selected", Toast.LENGTH_SHORT).show()
}
override fun onNothingSelected(p0: AdapterView<*>?) {
//empty
}
// insert code that activates data pull of tripsheet for driver= actifavte by method the class/object that activates the data pull. so datapuul(Driver)
}
limitDropDownHeight(spinner)
//drivers end
val btnLoadData: Button = findViewById(R.id.btnLoadData)
// weightsum(tvTotalweight, Tripsheetlist)
// totaldelNotes(tvTotaldelv,Tripsheetlist)
// setData(btnLoadData, Tripsheetlist )
fetchJson(spinner)
}
private fun fetchJson(spinner: Spinner) {
println("Attempting to Fetch JSON")
val url = "https://api.letsbuildthatapp.com/youtube/home_feed"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
println("Failed to execute request") }
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
println(body)
val gson = GsonBuilder().create()
val tripsheetlist = gson.fromJson(body, Tripsheetlist::class.java)
runOnUiThread {
recyclerViewTripsheetlist.adapter = TableViewAdapter(tripsheetlist, spinner)
}
}
})
}
In my Adapter class I then called it with : val spinner = spnDriver
This led to a different error: AppCompatSpinner.setOnItemSelectedListener(android.widget.AdapterView$OnItemSelectedListener)' on a null object reference
But seems like it passed the val spinner =spnDriver without a problem.
Thank you for all input and help.
I found a solution. What I did was to keep the spinner inside my MainActivity and then just pass the result of the spinner to the adapter - where I wanted to use it.

ViewModel Instance inside RecycleView KOTLIN CANNOT CREATE INSTANCE OF VIEWMODAL

Im trying to create a View model that contains alist of countries in it.
The View modal class look like this:
class Country_ViewModel(ctx:Context) :ViewModel(){
val itemSelected : MutableLiveData<Int> by lazy{
MutableLiveData<Int>()
}
val p = XmlPullParserHandler()
private var count: MutableList<Country> = p.parse(openCountriesFile(ctx))
val countryArray =MutableLiveData(count)
// This function will open the XML file and return an input stream that will be used by the Parse function
fun openCountriesFile(context: Context): InputStream? {
val assetManager: AssetManager = context.getAssets()
var `in`: InputStream? = null
try {
`in` = assetManager.open("countries.xml")
} catch (e: IOException) {
e.printStackTrace()
}
return `in`
}
// This function will loop thorough the country list and delete the entry that it got from the position
fun removeItem(position: Int) {
count.map { pos ->
if (pos.compare(count[position]) == 0) {
count.remove(pos)
return
}
}
}
The function openCountriesFile will just parse the XML file that contains the Countries and save it in the MutableLiveData object inside the ModelView.
Later I would like to use a Fragment to observe the data that is changed:
This fragment will use the Adapter that I created and populate the Fragment with the country data.
The fragment will look like that:
class frag : Fragment(){
val KEY_COUNTRY = "country"
val KEY_NAME = "name"
val KEY_FLAG = "flag"
val KEY_ANTHEM = "anthem"
val KEY_SHORT = "short"
val KEY_DETAILS = "details"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
val viewModal:Country_ViewModel by viewModels()
/*
* When creating the view we would like to do the following:
* Initiate the Adapter.
* When the adapter has been called he will look for the XML file with the country's in it.
Second one for the anthems
* */
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v = inflater.inflate(R.layout.fragment_frag, container, false)
val rv = v.findViewById(R.id.rvTodos) as RecyclerView
val adapter = countryAdapter(requireContext(),viewModal,this)
viewModal.itemSelected.observe(viewLifecycleOwner, Observer<Int>{
val fragment2 = details_frag()
val fragmentManager: FragmentManager? = fragmentManager
val fragmentTransaction: FragmentTransaction = fragmentManager!!.beginTransaction()
fragmentTransaction.apply {
replace(R.id.fragLand, fragment2)
commit()
}
})
rv.adapter = adapter
// Apply the new content into the fragment layout
val mLayoutManager = LinearLayoutManager(activity);
rv.layoutManager = mLayoutManager
return v
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater){
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.primary_menu,menu)
}
}
Theer I would observe if there was a country that has been clicked on and if so I would like to move to my other fragment to get some more details.
My adapter look like that:
class countryAdapter(
var ctx: Context, var viewModal:Country_ViewModel, var owner: LifecycleOwner
) : RecyclerView.Adapter<countryAdapter.countryViewHolder>() {
// lateinit var mListener: onItemLongClickListener
// private lateinit var mSListener: onItemClickListener
var player: MediaPlayer? =null
private lateinit var count: MutableList<Country>
/**-----------------------------------INTERFACES --------------------------------------------------*/
// interface onItemLongClickListener {
//
// fun onItemLongClick(position: Int)
// }
//
// interface onItemClickListener {
//
// fun onItemClick(position: Int): Boolean
// }
/**-----------------------------LISTENERS --------------------------------------------------------*/
// fun setOnItemLongClickListener(listener: onItemLongClickListener) {
// mListener = listener
//
// }
// fun setOnItemClickListener(listener: onItemClickListener) {
// mSListener = listener
// }
/**-----------------------------INNER CLASS--------------------------------------------------------*/
inner class countryViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView) {
val counrtyName = itemView.findViewById<TextView>(R.id.countryName)
val populationNum = itemView.findViewById<TextView>(R.id.countryPopulation)
val imageCount = itemView.findViewById<ImageView>(R.id.imageView)
/*
* Defining the listeners in the initialization of the Row in the adapter
* */
init {
count= viewModal.countryArray.value!!
itemView.setOnLongClickListener {
viewModal.removeItem(adapterPosition)
return#setOnLongClickListener true
}
itemView.setOnClickListener{
viewModal.itemSelected.value=adapterPosition
Log.i("Hello",adapterPosition.toString())
startPlayer(adapterPosition,ctx)
}
viewModal.countryArray.observe(owner, Observer {
notifyDataSetChanged()
})
}
}
/**---------------------------------------VIEW HOLDER CREATE AND BIND ----------------------------- */
/*
* Will inflate the country XML file in the adapter and then inflate it into the parent that is
* the fragment.
* At the end it will return the inner class with all the parameters that was initiated there.
* */
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): countryAdapter.countryViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.itemcountry, parent, false)
val context = parent.context
val inflater = LayoutInflater.from(context)
val contactView = inflater.inflate(R.layout.itemcountry, parent, false)
return countryViewHolder(contactView)
}
/*
* The function will be responsible to get the data that was initiated at the begining in the
* inner class and change the data that is displayed in the XML file to the new data based on the
* Country that it got
* The position is parameter that is changing every time this function is called and adding all the
* Country that are in the XML into the fragment
*
* */
override fun onBindViewHolder(holder: countryViewHolder, position: Int) {
var countryName1 = holder.counrtyName
var countryPopulation1 = holder.populationNum
var imagecount = holder.imageCount
countryName1.setText(viewModal.countryArray.value?.get(position)?.name_of_country)
countryPopulation1.setText(count?.get(position)?.shorty_of_country)
count?.get(position)?.let {
country_drawable.get(it.name_of_country)?.let {
imagecount.setBackgroundResource(
it
)
}
}
}
/**-----------------------------------------Functions ------------------------------------------- */
fun startPlayer(position: Int,ctx:Context){
player?.stop()
player =
count?.get(position)
?.let { country_raw.get(it.name_of_country)?.let { MediaPlayer.create(ctx, it) } }
player?.start()
}
override fun getItemCount(): Int {
return count.size
}
}
The goal is if the user click on one of the countries in the RecyclyView (OnClickListener) then i would like to move to the second fragment.
Im having an error will creating the viewModal instance the error is:
Cannot create an instance of class com.example.Country_ViewModel
Why is that? what I'm initializing wrong?
Where should i create the instance of the ViewModal? inside the adapter or inside the fragment itself? ( is it ok to pass the viewModal instance to the adapter? or there is another way i can observe the change in the CountryArray?)
Thank you.

Kotlin RecyclerView not updating after data changes

I am using RecyclerView to display a dynamic list of data and after I call an api I need to update my RecyclerView UI but the items in my RecyclerView does not change...
Below is my how I init my RecyclerView in my Fragment:-
forwardedList.layoutManager = LinearLayoutManager(context!!, RecyclerView.VERTICAL, false)
adapter = ForwardListAdapter(SmsHelper.getForwardedSms(context!!))
forwardedList.adapter = adapter
Below is my custom RecyclerView Adapter:-
class ForwardListAdapter(val forwardedList: List<SmsData>) : RecyclerView.Adapter<ForwardListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ForwardListAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.forwarded_item, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ForwardListAdapter.ViewHolder, position: Int) {
holder.bindItems(forwardedList[position])
}
override fun getItemCount(): Int {
return forwardedList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItems(sms: SmsData) {
val simSlotText: TextView = itemView.findViewById(R.id.simSlot)
val senderText: TextView = itemView.findViewById(R.id.sender)
simSlotText.text = "[SIM ${sms.simSlot}] "
senderText.text = sms.sender
}
}
}
I am currently updating my RecyclerView from SmsHelper class as below:-
val fragments = mainActivity!!.supportFragmentManager.fragments
for (f in fragments) {
if (f.isVisible) {
if (f.javaClass.simpleName.equals("ForwardedFragment")) {
val fg = f as ForwardedFragment
fg.adapter.notifyDataSetChanged() <----- HERE
} else if (f.javaClass.simpleName.equals("FailedFragment")) {
val fg = f as FailedFragment
fg.adapter.notifyDataSetChanged()
}
}
}
As I observed, you did not really change the adapter's data but only called notifyDataSetChanged. You cannot just expect the data to be changed automatically like that since notifyDataSetChanged only:
Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself.
You need to change the data by yourself first, then call notifyDataSetChanged.
class ForwardListAdapter(private val forwardedList: MutableList<SmsData>) : RecyclerView.Adapter<ForwardListAdapter.ViewHolder>() {
// ...
fun setData(data: List<SmsData>) {
forwardedList.run {
clear()
addAll(data)
}
}
// ...
}
Then do it like this:
adapter.run {
setData(...) // Set the new data
notifyDataSetChanged(); // notify changed
}