android - Change RecyclerView Item icon through Adapter - android

So, I'm creating an app, which has a RecyclerView in the settings. The RecyclerView items, contain a cardview, containing an ImageView, used to represent the item of the RecyclerView. What i need to do is replacing the placeholder ImageView with the newer icon, give trough the fragment that loads the RecyclerView. I created my RecyclerView in Kotlin following this tutorial.
I tried creating a value with type ImageView, Image and Bitmap in the data class, however the functions suggested dont work.
How do you create an ImageView value in a class? How do you change a RecyclerView ImageView content?
dataListIcon.kt
import android.widget.ImageView
data class dataListIcons (
val stringTitle: String,
val stringDescription: String,
val imageIcon: ImageView //Here i tried creating the ImageView value
)
fragmentSettings.kt
class frgSettingsMain : Fragment() {
val listsettings = listOf(
dataListIcon("Option", "Description of option", R.drawable.ic_outline_color_lens_24),
dataListIcon("Option", "Description of option", R.drawable.ic_outline_dashboard_24),
dataListIcon("Option", "Description of option", R.drawable.ic_outline_image_24),
dataListIcon("Option", "Description of option", R.drawable.ic_outline_volume_up_24),
dataListIcon("Option", "Description of option", R.drawable.ic_outline_library_music_24)
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_settingsmain, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
rvSettingsMain.apply {
layoutManager = LinearLayoutManager(activity)
adapter = adapterSettingsMain(listsettings)
}
}
companion object {
fun newInstance(): frgSettingsMain = frgSettingsMain()
}
}
adapterSettings.kt
class adapterSettingsMain(
var listsettings: List<dataItems>
) : RecyclerView.Adapter<adapterSettingsMain.SettingsViewHolder>() {
inner class SettingsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_settingsicon, parent, false)
return SettingsViewHolder(view)
}
override fun getItemCount(): Int {
return listsettings.size
}
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.itemView.apply {
rvSettingsTitle.text = listsettings[position].stringTitle
rvSettingsDescription.text = listsettings[position].stringDescription
rvSettingsIcon //TODO
}
}
}

val imageIcon: ImageView //Here i tried creating the ImageView
value
Change ImageView type to int as R.drawable.ic_outline_color_lens_24 is Integer type
data class dataListIcons (
val stringTitle: String,
val stringDescription: String,
val imageIcon Int //Here i tried creating the ImageView value
)
Add this Inside your onBindViewHolder(...) method
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.itemView.apply {
rvSettingsTitle.text = listsettings[position].stringTitle
rvSettingsDescription.text = listsettings[position].stringDescription
rvIcon.setImageResource(listsettings[position].imageIcon);//rvIcon is id of ImageView from R.layout.item_settingsicon
}
}
Add <ImageView..../> inside your R.layout.item_settingsicon

Solution 1:
Simply use Glide for image rendering in Imageviews.
It will be more flexible with more features use can use with it.
val imageView : ImageView = ..initialise it..
Glide.with(imageView).placeholder(R.drawable.your_placeholder).into(imageView)
Dependency:
implementation 'com.github.bumptech.glide:glide:4.11.0'
Solution 2:
val imageView : ImageView = ..initialise it..
imageView.setImageResource(R.drawable.your_resource)

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 to update fragmentStateAdapter after a bottomSheet dismiss

I have this fragmentStateAdapter which holds some of my Cards in order to swipe among them:
public class ScreenSlidePagerAdapter(activity: AppCompatActivity, var items: ArrayList<Helpers.Card>, private val saveToDb: Boolean) : FragmentStateAdapter(activity) {
override fun getItemCount(): Int = items.count()
var fragment:CardItemFragment?=null
override fun createFragment(position: Int): Fragment{
fragment =CardItemFragment(position, saveToDb ,this) //.newInstance( items[position ].getSaveString())
return fragment as CardItemFragment
}}
and the CardItemFragement is:
class CardItemFragment(val position:Int, val saveToDb: Boolean, val isCardMine: Boolean,val adapter: ScreenSlidePagerAdapter) : Fragment() {
lateinit var cardView1: ConstraintLayout
lateinit var crdNameTB1: TextView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val v = inflater.inflate(R.layout.card_item, container, false)
cardView1 = v.findViewById(R.id.cardView1)
crdNameTB1= v.findViewById(R.id.crdNameTB1)
return v
}
Then I can update the Card inside the fragementStateAdapter like this:
fun updateCard(): Bitmap? {
crdNameTB1.text = .....
}
That's works fine and I can swipe among my Cards, but when I change the value of "name" attribute of my Card via a bottom sheet and when I dismiss the BottomSheet I use this code to refresh the fragmentSatateAdapter:
MainActivity.galleryPagerAdapter?.fragment ?.updateCard()
I set MyActivity as companion object to reach it anywher.
When this code executed, I got the fragment not null object but the updateCard gives me null objects
1what is the problem and how can I overcome this and update the shown Card?
I have found the answer by overriding the following methods in order to make the Adapter run
override fun getItemId(position: Int): Long {return items[position].id}
override fun containsItem(itemId: Long): Boolean = items.any { it.id == itemId }
the answer post is here

android - getString causes error getting from strings.xml

Im currently working on an app which has a RecyclerView for the Settings Menu. My objective is to use strings saved in strings.xml for the RecyclerView, because my app has localization;
In my case, i generate the RecyclerView with a list saved in the fragment, which needs strings from string.xml, however, trying to call strings from the xml dosnt work.
How do you pass strings from string.xml as parameters? How can i pass im this scenario?
frgSettingsMain.kt
class frgSettingsMain : Fragment() {
val listsettings = listOf(
//using #string/string here doesnt work!
dataListIcons(#string/..., "Description of option", R.drawable.ic_outline_color_lens_24),
dataListIcons(I WANT THE STRING HERE, "Description of option", R.drawable.ic_outline_dashboard_24),
dataListIcons("Option", "Description of option", R.drawable.ic_outline_image_24),
dataListIcons("Option", "Description of option", R.drawable.ic_outline_volume_up_24),
dataListIcons("Option", "Description of option", R.drawable.ic_outline_library_music_24)
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_settingsmain, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
rvSettingsMain.apply {
layoutManager = LinearLayoutManager(activity)
adapter = adapterSettingsMain(listsettings)
}
}
companion object {
fun newInstance(): frgSettingsMain = frgSettingsMain()
}
}
dataListIcons.kt
data class dataListIcons (
val stringTitle: String,
val stringDescription: String,
val imageIcon: Int
)
adapterSettingsMain.kt
package com.meltixdev.revomusicplayer
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_settingsicon.view.*
class adapterSettingsMain(
var listsettings: List<dataListIcons>
) : RecyclerView.Adapter<adapterSettingsMain.SettingsViewHolder>() {
inner class SettingsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_settingsicon, parent, false)
return SettingsViewHolder(view)
}
override fun getItemCount(): Int {
return listsettings.size
}
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.itemView.apply {
rvSettingsTitle.text = listsettings[position].stringTitle
rvSettingsDescription.text = listsettings[position].stringDescription
rvSettingsIcon.setImageResource(listsettings[position].imageIcon)
}
}
}
use getString(R.string.stringname)
Edit
will make some changes to your code
data class dataListIcons (
val stringTitle: Int,
val stringDescription: Int,
val imageIcon: Int
)
val listsettings = listOf(
dataListIcons(R.string.title1, R.string.desription1,
R.drawable.ic_outline_color_lens_24),
dataListIcons(R.string.title2, R.string.desription2,
R.drawable.ic_outline_dashboard_24),
.........
)
in your adapter
holder.itemView.apply {
rvSettingsTitle.text = this.context.getString(listsettings[position].stringTitle)
rvSettingsDescription.text = this.context.getString(listsettings[position].stringDescription)
rvSettingsIcon.setImageResource(listsettings[position].imageIcon)
}
you can always pass the string resource ids.
something like below:
fun getList(): List<Int> {
return listOf(R.string.app_name, R.string.app_name, R.string.app_name)
}
and wherever you need to get the string from ids just call
context?.getString(getList()[position])

How to Read Data from RecyclerView and send in BottomSheet

I am using recyclerView to show list of apps installed in device
image - Link
and for more details I use bottomSheet on LongPress i.e. in ViewHolder Class, but How to send data of selected tab to bottomSheet with more details(such as package name, API level etc.)...for reference see images
I want - Link
I get from below coding - Link
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView.adapter = Adapter // I set adapter here with function getApps()
recyclerView.layoutManager = LinearLayoutManager(this)
private fun getApps(): List<DataClass> {
// here I get apps icon,name,size and return list<DataClass>
return list
}
Adapter.kt
class Adapter(private val listOfApps: List<AppData>) :
RecyclerView.Adapter<Adapter.ViewHolder>() {
class ViewHolder(appView: View) : RecyclerView.ViewHolder(appView), View.OnClickListener,
View.OnLongClickListener {
init {
appView.setOnClickListener(this)
appView.setOnLongClickListener(this)
}
val icon: ImageView = appView.App_icon
val name: TextView = appView.App_name
val size: TextView = appView.App_size
override fun onClick(v: View?) {
Toast.makeText(v?.context, "OnClick", Toast.LENGTH_SHORT).show()
}
override fun onLongClick(v: View?): Boolean {
// I want here on Long press BottomSheet appears with details
val bottomSheetDialog = BottomSheetDialog()
// Show bottomSheet on LongPress
bottomSheetDialog.show(
(v?.context as FragmentActivity).supportFragmentManager, bottomSheetDialog.tag
)
return true
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(
R.layout.list_apps, parent, false
)
return ViewHolder(view)
}
override fun getItemCount() = listOfApps.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = listOfApps[position]
holder.icon.setImageDrawable(currentItem.icon)
holder.name.text = currentItem.name
holder.size.text = currentItem.size
}
}
BottomSheetDialog.kt
class BottomSheetDialog: BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.bottom_sheet, container, false)
}
override fun getTheme(): Int = R.style.RoundBottomSheetDialog
}
DataClass
data class AppData(
val icon: Drawable,
val name: String,
val size: String,
)
With your current code the easiest solution would be:
Modify your BottomSheetDialog to include AppData in the constructor
class BottomSheetDialog(val appData: AppData): BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.bottom_sheet, container, false)
}
override fun getTheme(): Int = R.style.RoundBottomSheetDialog
}
Add onBind method inside your ViewHolder class:
fun onBind(appData: AppData) {
icon.setImageDrawable(currentItem.icon)
name.text = currentItem.name
size.text = currentItem.size
}
Modify your onBindViewHolder method inside the Adapter to call that onBind method:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.onBind(listOfApps[position])
}
Inside your ViewHolder add lateinit var currentItem: AppData that will be set inside onBind and we can use it in onLongClick:
class ViewHolder(appView: View) : RecyclerView.ViewHolder(appView), View.OnClickListener,
View.OnLongClickListener {
.
.
.
override fun onLongClick(v: View?): Boolean {
// I want here on Long press BottomSheet appears with details
**val bottomSheetDialog = BottomSheetDialog(currentItem)**
// Show bottomSheet on LongPress
bottomSheetDialog.show(
(v?.context as FragmentActivity).supportFragmentManager, bottomSheetDialog.tag
)
return true
}
**private lateinit var currentItem: AppData**
fun onBind(appData: AppData) {
**currentItem = appData**
icon.setImageDrawable(currentItem.icon)
name.text = currentItem.name
size.text = currentItem.size
}
attach your currentItem list item to ViewHolder inside onBindViewHolder and inside onLongClick pass this data to freshly created BottomSheetDialog, which is extending Fragment (so you may use Bundle - setArgument method). then inside onCreateView set your data to Views
Create an interface
interface OnListItemClicked {
fun onClicked(data : AppData)
}
Implement that interface in BottomSheetDialog
class BottomSheetDialog: BottomSheetDialogFragment(), OnListItemClicked {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.bottom_sheet, container, false)
}
override fun onClicked(data: AppData) {
//show Details here
}
override fun getTheme(): Int = R.style.RoundBottomSheetDialog
}
Pass extra callback to the adapter from main activity.
Adapter(private val listOfApps: List<AppData>, val onClick: (AppData) -> Unit)
Invoke the click listener from adapter with listOfAppData[position]
On MainActivity pass a function into the Adapter initialisation.
fun passDataToBottomSheet(data: AppData) {
val listener = OnListItemClicked()
listener.onClicked(data)
}
Adapter(list, ::passDataToBottomSheet)
That's it. It should work now.

notifyDataSetChanged not updating RecyclerView although ArrayList reference is not lost

I appreciate that this question has been asked MANY times in SO but all the solutions refer to a missing reference in my arraylist which currently I (believe) am preserving.
I have an adapter as follows:
/**
* [RecyclerView.Adapter] that can display a [Note].
*/
class MyNoteRecyclerViewAdapter(
private var notes: ArrayList<Note>,
private val onNoteSelectedListener: MainActivityContract.OnNoteSelectedListener
) : RecyclerView.Adapter<MyNoteRecyclerViewAdapter.ViewHolder>() {
private val mNotes: ArrayList<Note> = notes
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_notes_list, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = mNotes[position]
holder.bind(item, onNoteSelectedListener)
}
override fun getItemCount(): Int = mNotes.size
fun setItems(notes: ArrayList<Note>) {
mNotes.clear()
mNotes.addAll(notes)
notifyDataSetChanged()
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val idView: TextView = view.findViewById(R.id.item_number)
private val contentView: TextView = view.findViewById(R.id.content)
fun bind(item: Note, onNoteSelectedListener: MainActivityContract.OnNoteSelectedListener) {
idView.text = item.id
contentView.text = item.title
itemView.setOnClickListener(View.OnClickListener { onNoteSelectedListener.onNoteSelected(item) })
}
override fun toString(): String {
return super.toString() + " '" + contentView.text + "'"
}
}
}
as you can see above, the function setItems() is setting clearing all notes in the list and readding them followed by notifyDataSetChanged()
The view that calls this is here:
class NotesListFragment() : BaseFragment(), MainActivityContract.OnNoteSelectedListener {
var mAdapter: MyNoteRecyclerViewAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_notes_list_list, container, false)
val lvNotes = view.findViewById<RecyclerView>(R.id.lv_notes);
mAdapter = MyNoteRecyclerViewAdapter(ArrayList<Note>(), this)
lvNotes.adapter = mAdapter
return view
}
override fun onNoteSelected(note: Note) {
(activity as MainActivity).onNoteSelected(Note());
}
fun onNotesLoaded(notes: ArrayList<Note>) {
mAdapter?.setItems(notes)
view?.findViewById<RecyclerView>(R.id.lv_notes)?.adapter?.notifyDataSetChanged()
}
}
The function onNotesLoaded is called by an external class that fetches notes. As you can see, I am setting items using mAdapter.setItems() which should notify the list that data has changed but without luck. I tried to also add the second line to see if it's something I'm missing but again, no luck.
I'm not sure if this is a kotlin issue on my part when assigning the list variable but any assistance would be greatly appreciated.
It seem you forgot add LayoutManager for RecyclerView
mAdapter = MyNoteRecyclerViewAdapter(ArrayList<Note>(), this)
lvNotes.adapter = mAdapter
lvNotes.layoutManager = LinearLayoutManager(context)
It seems like the issue lied in other areas of my codebase so I will close this question. Thanks for anyone who tried to help

Categories

Resources