How to get the selected item from ListView in Kotlin? - kotlin

Code Sample:
package tech.kapoor.listviewdemo
import android.content.Context
import android.graphics.Color
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ListView
import android.widget.TextView
import android.widget.AdapterView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val listView = findViewById<ListView>(R.id.main_listview)
var redColor = Color.parseColor("#FF0000")
listView.setBackgroundColor(redColor)
listView.adapter = CustomAdapter(this)
}
private class CustomAdapter(context: Context): BaseAdapter() {
private val mContext: Context
init {
mContext = context
}
override fun getCount(): Int {
return 80
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItem(position: Int): Any {
return position
}
override fun getView(position: Int, view: View?, viewGroup: ViewGroup?): View {
val textView = TextView(mContext)
textView.text = "Here comes the !!"
return textView
}
}
}
I'm trying to understand list view instead of recycler view to understand basics first.
Anybody knows how we get the selected row id/index value on selection or onclick and also how to perform some action on selection of a specific row in kotlin?

To populate listview you must have dataset. Dataset may be any list of either datatypes like Strings or you can use list of model class. Something like this:
This is my simple list of dataset which I will use in ListView:
val data = ArrayList<TopicDTO>()
data.add(TopicDTO("1", "Info 1", true))
data.add(TopicDTO("2", "Info 2", false))
data.add(TopicDTO("3", "Info 3", true))
data.add(TopicDTO("4", "Info 4", false))
I have created one model class named TopicDTO which contains id,title and its status.
Now let's populate this into ListView:
list.adapter = ButtonListAdapter(baseContext, data)
Here is a simple adapter:
class ButtonListAdapter(//Class for rendering each ListItem
private val context: Context, private val rowItems: List<TopicDTO>) : BaseAdapter() {
override fun getCount(): Int {
return rowItems.size
}
override fun getItem(position: Int): Any {
return rowItems[position]
}
override fun getItemId(position: Int): Long {
return rowItems.indexOf(getItem(position)).toLong()
}
private inner class ViewHolder {
internal var main_text: TextView? = null //Display Name
internal var subtitle: TextView? = null //Display Description
internal var can_view_you_online: Button? = null //Button to set and display status of CanViewYouOnline flag of the class
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var convertView = convertView
var holder: ViewHolder? = null
val mInflater = context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE) as LayoutInflater
holder = ViewHolder()
if (convertView == null) {
convertView = mInflater.inflate(R.layout.main_lp_view_item, null)
holder.main_text = convertView!!.findViewById(R.id.main_lp_text) as TextView
holder.subtitle = convertView.findViewById(R.id.main_lp_subtitle) as TextView
holder.can_view_you_online = convertView.findViewById(R.id.can_view_you_online) as Button
convertView.tag = holder
} else {
holder = convertView.tag as ViewHolder
}
val rowItem = rowItems[position]
val main_text: String
val subtitle: String
holder.main_text!!.text = rowItem.info
holder.subtitle!!.text = rowItem.info
if (rowItem.canViewYouOnline) {
holder.can_view_you_online!!.setBackgroundColor(context.resources.getColor(R.color.colorPrimary))
} else {
holder.can_view_you_online!!.setBackgroundColor(context.resources.getColor(R.color.colorAccent))
}
holder.can_view_you_online!!.setOnClickListener(object : View.OnClickListener {
internal var buttonClickFlag: Boolean = false
override fun onClick(v: View) { //The Onclick function allows one to click the button on the list item and set/reset the canViewYouOnline flag. It is working fine.
}
})
return convertView
}
}
Now you can get your selected item like this:
list.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
// This is your listview's selected item
val item = parent.getItemAtPosition(position) as TopicDTO
}
Hope you understands this.

You can use inside the getView() method something like:
view.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
//use getItem(position) to get the item
}
})
or using the lambda:
view.setOnClickListener({ v -> //use theItem(position) })
Just a tip:
I'm trying to understand list view instead of recycler view to understand basics first.
In my opinion in your projects you will use RecyclerView in 99% of the cases.

add OnItemClickListener in you oncreate()
listView.setOnItemClickListener{ parent, view, position, id ->
Toast.makeText(this, "You Clicked:"+" "+position,Toast.LENGTH_SHORT).show()
}
Add the array of Items in your CustomAdapter class.
class CustomAdptor(private val context: Activity): BaseAdapter() {
//Array of fruits names
var names = arrayOf("Apple", "Strawberry", "Pomegranates", "Oranges", "Watermelon", "Bananas", "Kiwi", "Tomato", "Grapes")
//Array of fruits desc
var desc = arrayOf("Malus Domestica", "Fragaria Ananassa ", "Punica Granatum", "Citrus Sinensis", "Citrullus Vulgaris", "Musa Acuminata", "Actinidia Deliciosa", "Solanum Lycopersicum", "Vitis vinifera")
//Array of fruits images
var image = intArrayOf(R.drawable.apple, R.drawable.strawberry, R.drawable.pomegranates, R.drawable.oranges, R.drawable.watermelon, R.drawable.banana, R.drawable.kiwi, R.drawable.tomato, R.drawable.grapes)
override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View {
val inflater = context.layoutInflater
val view1 = inflater.inflate(R.layout.row_data,null)
val fimage = view1.findViewById(R.id.fimageView)
var fName = view1.findViewById(R.id.fName)
var fDesc = view1.findViewById(R.id.fDesc)
fimage.setImageResource(image[p0])
fName.setText(names[p0])
fDesc.setText(desc[p0])
return view1
}
override fun getItem(p0: Int): Any {
return image[p0]
}
override fun getItemId(p0: Int): Long {
return p0.toLong()
}
override fun getCount(): Int {
return image.size
}
}
You can find the whole tutorial at: listview with onItemClickListener using kotlin

Related

RecycleView selection library - holding an item fires onSelectionChanged several times

I implemented a simple RecyclerView example, with multiple items selection possibility, using the selection library.
Here's my code:
class MainActivity : AppCompatActivity() {
private var listOfItems = ArrayList<Item>()
private var selectionTracker: SelectionTracker<Long>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.rvMainRecyclerView)
for (i in 0..32) {
listOfItems.add(Item(i, "Item $i"))
}
val recyclerViewAdapter = RecyclerViewAdapter(listOfItems)
recyclerView.adapter = recyclerViewAdapter
selectionTracker = SelectionTracker.Builder<Long>(
"Itemselection",
recyclerView,
MyItemKeyProvider(recyclerView),
MyItemDetailsLookup(recyclerView),
StorageStrategy.createLongStorage()
).build()
selectionTracker?.addObserver(
object: SelectionTracker.SelectionObserver<Long>() {
override fun onSelectionChanged() {
super.onSelectionChanged()
val itemsSelected = selectionTracker?.selection?.size()
Log.d("MainActivity", "$itemsSelected items selected")
}
}
)
recyclerViewAdapter.tracker = selectionTracker
}
}
class RecyclerViewAdapter(
val dataSet: ArrayList\<Item\>
): RecyclerView.Adapter\<RecyclerViewAdapter.ViewHolder\>() {
var tracker: SelectionTracker<Long>? = null
init {
setHasStableIds(true)
}
inner class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
val textView: TextView
init {
textView = view.findViewById(R.id.tvRecyclerViewItemTitle)
}
fun getItemDetails(): ItemDetails<Long> {
return object : ItemDetails<Long>() {
override fun getPosition(): Int {
return adapterPosition
}
override fun getSelectionKey(): Long? {
return itemId
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater
.from(parent.context).inflate(R.layout.recycler_view_item, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = dataSet[position]
holder.textView.text = currentItem.value
holder.itemView.isActivated = tracker!!.isSelected(position.toLong())
}
override fun getItemCount(): Int {
return dataSet.size
}
override fun getItemId(position: Int): Long {
return dataSet[position].id.toLong()
}
}
I'm curious why the onSelectionChanged (in MainActivity selectionTracker?.addObserver...) is triggered several times while I hold an item to start selection mode?
To be more precise, this is the use case:
Nothing is selected,
Long click on any item => selection mode activated; the item is selected
Here, "1 items selected" is being printed out as long as I hold the first item.

Change API param based on Dialog Fragment input with MVVM in Kotlin

i'm a beginner in android & kotlin and i'm having an issue i been trying to figure out all day...
I have an app that fetches data from NewsApi and displays it in a recycler view , i am using Retrofit library and Room (to save favorite articles) with MVVM architecture. I want to add an option so that the user can select the country of the news from a dialog that pops up by clicking on a icon on the toolbar menu.
I have created a custom DialogFragment and have it show up, the dialog contains a spinner with a list of countries and i'm using FragmentResult and FragmentResultListener to pass the country value between dialog fragment and news fragment.
DialogFragment
class CountrySelectDialog : DialogFragment(R.layout.country_selection_dialog) {
private lateinit var binding: CountrySelectionDialogBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = CountrySelectionDialogBinding.bind(view)
binding.spCountrySelection.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
adapterView: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
Toast.makeText(
context,
"you selected ${adapterView?.getItemAtPosition(position).toString()}",
Toast.LENGTH_SHORT
).show()
}
override fun onNothingSelected(adapterView: AdapterView<*>?) {
}
}
binding.btnCancel.setOnClickListener {
this.dismiss()
}
binding.btnConfirm.setOnClickListener {
val result = binding.spCountrySelection.selectedItem.toString()
setFragmentResult("countryCode", bundleOf("bundleKey" to result))
this.dismiss()
}
}
}
The news Fragment is observing data from the View Model
class BreakingNewsFragment : Fragment(R.layout.fragment_breaking_news) {
lateinit var viewModel: NewsViewModel
lateinit var newsAdapter: NewsAdapter
private lateinit var binding: FragmentBreakingNewsBinding
val TAG = "BreakingNewsFragment"
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentBreakingNewsBinding.bind(view)
viewModel = (activity as NewsActivity).viewModel
setUpRecyclerView()
setFragmentResultListener("countryCode") { countryCode, bundle ->
val result = bundle.getString("countryCode")
viewModel.countryCode = result!!}
viewModel.breakingNews.observe(viewLifecycleOwner, Observer {
when (it) {
is Resource.Success -> {
hideProgressBar()
it.data?.let {
newsAdapter.differ.submitList(it.articles.toList())
val totalPages = it.totalResults / QUERY_PAGE_SIZE + 2
isLastPage = viewModel.breakingNewsPage == totalPages
}
}
is Resource.Error -> {
hideProgressBar()
it.message?.let {
Log.e(TAG, "An error occurred: $it")
}
}
is Resource.Loading -> {
showProgressBar()
}
}
})
newsAdapter.setOnItemClickListener {
val bundle = Bundle().apply {
putSerializable("article", it)
}
findNavController().navigate(
R.id.action_breakingNewsFragment_to_articleFragment, bundle
)
}
}
ViewModel:
class NewsViewModel(val newsRepository: NewsRepository, val app: Application) : AndroidViewModel(app) {
val breakingNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
var breakingNewsPage = 1
var breakingNewsResponse: NewsResponse? = null
val searchNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
var searchNewsPage = 1
var searchNewsResponse: NewsResponse? = null
var countryCode :String = "it"
init {
getBreakingNews(countryCode)
}
fun getBreakingNews(countryCode: String) {
viewModelScope.launch {
breakingNews.postValue(Resource.Loading())
val response = newsRepository.getBreakingNews(countryCode, breakingNewsPage)
breakingNews.postValue(handleBreakingNewsResponse(response))
}
}
fun handleBreakingNewsResponse(response: Response<NewsResponse>): Resource<NewsResponse> {
if (response.isSuccessful) {
response.body()?.let { resultResponse ->
breakingNewsPage++
if (breakingNewsResponse == null) {
breakingNewsResponse = resultResponse
} else {
val oldArticles = breakingNewsResponse?.articles
val newArticles = resultResponse.articles
oldArticles?.addAll(newArticles)
}
return Resource.Success(breakingNewsResponse ?: resultResponse)
}
}
return Resource.Error(response.message())
}
fun searchNews(searchQuery: String) {
viewModelScope.launch {
searchNews.postValue(Resource.Loading())
val response = newsRepository.searchNews(searchQuery, searchNewsPage)
searchNews.postValue((handleSearchNewsResponse(response)))
}
}
fun handleSearchNewsResponse(response: Response<NewsResponse>): Resource<NewsResponse> {
if (response.isSuccessful) {
response.body()?.let { resultResponse ->
searchNewsPage++
if (searchNewsResponse == null) {
searchNewsResponse = resultResponse
} else {
val oldArticles = searchNewsResponse?.articles
val newArticles = resultResponse.articles
oldArticles?.addAll(newArticles)
}
return Resource.Success(searchNewsResponse ?: resultResponse)
}
}
return Resource.Error(response.message())
}
}
When i click on the icon on the toolbar menu the dialog appears and works fine but i can't seem to find a way to have the recycler view update with new data using given value for country
I searched everywhere and couldn't find a solution (or probably didn't understand it :S) can someone guide me into the right direction? I'm so lost...
When I click on the icon on the toolbar menu the dialog appears and works fine but I can't seem to find a way to have the recycler view update with new data using given value for country.

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
}