Getting a large number of exceptions which I cannot reproduce like these:-
Fatal Exception: java.lang.IllegalArgumentException: view is not a
child, cannot hide androidx.cardview.widget.CardView{5dd439c V.E...C..
......I. 20,-228-680,-78}
at androidx.recyclerview.widget.b.e(ChildHelper.java:352)
at androidx.recyclerview.widget.RecyclerView$p.b(RecyclerView.java:6393)
There is no link from my source. But using Crashlytics log I think I know the approximate area of problem. The recycler adapter row is a very simple layout with a cardview at the root and a relative layout as the child. What could be the cause of this exception ?
Edit: My adapter
internal class AnalyzerAdapter(private val fragment: AnalyzerFragment,
private val context: Context) : RecyclerView.Adapter<AnalyzerAdapter.Companion.ViewHolder>() {
private val entries = ArrayList<AnalyzerData>()
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
val parent = LayoutInflater.from(context).inflate(R.layout.analyzer_row, viewGroup, false)
return ViewHolder(parent)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val entry = entries[position]
holder.tvSize.text = "title"
holder.tvDetails.text = "details"
val type = getItemViewType(position)
holder.itemView.setOnClickListener {
fragment.onListClick(type)
}
when (type) {
...
}
}
override fun getItemViewType(position: Int): Int {
return entries[position].dataType
}
override fun getItemCount(): Int {
return entries.size
}
fun addEntry(entry: AnalyzerData) {
entries.add(entry)
notifyItemInserted(entries.size - 1)
}
fun editEntry(entry: AnalyzerData, position: Int) {
entries[position] = entry
notifyItemChanged(position)
}
fun initEntries(list: List<AnalyzerData>) {
entries.clear()
entries.addAll(list)
notifyDataSetChanged()
}
companion object {
internal class ViewHolder(parent: View) : RecyclerView.ViewHolder(parent) {
var ivIcon: ImageView = parent.findViewById(R.id.analyzer_icon)
var tvTitle: TextView = parent.findViewById(R.id.analyzer_title)
var tvDetails: TextView = parent.findViewById(R.id.analyzer_details)
var tvSize: TextView = parent.findViewById(R.id.analyzer_size)
}
}
}
Related
I created a RecyclerView that refreshes its list based on a database call. Each row has an options menu that is revealed when the user swipes. My original issue was that after an orientation change, the swipe gestures no longer revealed the menu. I hit all my expected breakpoints with onCreateViewHolder() and the onSwipe(). However, the row remained as the HIDE_MENU view type after swiping.
So I tried to introduce LiveData to persist the state of the list after orientation changes. The RecyclerView was still created and populated with items but now the swipe gesture crashes the application with an error:
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
Do I need to use LiveData to fix the original issue of preserving my swipe functionality after orientation changes? If not, please can someone explain why the item view types are no longer updated after orientation changes.
If I do need to use a ViewModel, what am I doing that is causing the list adapter not to receive the updated list?
HistoryFragment
class HistoryFragment : Fragment() {
private val historyViewModel by activityViewModels<HistoryViewModel>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.fragment_history, container, false)
historyViewModel.getHistoryList().observe(viewLifecycleOwner, {
refreshRecyclerView(it)
})
return root
}
private fun updateHistoryList() {
val dbHandler = MySQLLiteDBHandler(requireContext(), null)
val historyList = dbHandler.getHistoryList() as MutableList<HistoryObject>
historyViewModel.setHistoryList(historyList)
}
private fun refreshRecyclerView(historyList: MutableList<HistoryObject>) {
val historyListAdapter = HistoryListAdapter(historyList)
val callback = HistorySwipeHelper(historyListAdapter)
val helper = ItemTouchHelper(callback)
history_list.adapter = historyListAdapter
helper.attachToRecyclerView(history_list)
}
private fun setupSort() {
val sortSpinner: Spinner = history_list_controls_sort
sortSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
updateHistoryList()
}
}
}
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
setupSort()
}
}
HistoryListAdapter
const val SHOW_MENU = 1
const val HIDE_MENU = 2
class HistoryListAdapter(private var historyData: MutableList<HistoryObject>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == SHOW_MENU) {
val inflatedView = LayoutInflater.from(parent.context).inflate(R.layout.history_list_view_row_items_menu, parent, false)
MenuViewHolder(inflatedView)
} else {
val inflatedView = LayoutInflater.from(parent.context).inflate(R.layout.history_list_view_row_items_description, parent, false)
HistoryItemViewHolder(inflatedView)
}
}
override fun getItemViewType(position: Int): Int {
return if (historyData[position].showMenu) {
SHOW_MENU
} else {
HIDE_MENU
}
}
override fun getItemCount(): Int {
return historyData.count()
}
fun showMenu(position: Int) {
historyData.forEachIndexed { idx, it ->
if (it.showMenu) {
it.showMenu = false
notifyItemChanged(idx)
}
}
historyData[position].showMenu = true
notifyItemChanged(position)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item: HistoryObject = historyData[position]
if (holder is HistoryItemViewHolder) {
holder.bindItem(item)
...
}
if (holder is MenuViewHolder) {
holder.bindItem(item)
...
}
}
class HistoryItemViewHolder(v: View, private val clickHandler: (item: HistoryObject) -> Unit) : RecyclerView.ViewHolder(v) {
private var view: View = v
private var item: HistoryObject? = null
fun bindItem(item: HistoryObject) {
this.item = item
...
}
}
class MenuViewHolder(v: View, private val deleteHandler: (item: HistoryObject) -> Unit) : RecyclerView.ViewHolder(v) {
private var view: View = v
private var item: HistoryObject? = null
fun bindItem(item: HistoryObject) {
this.item = item
...
}
}
}
HistorySwipeHelper
class HistorySwipeHelper(private val adapter: HistoryListAdapter) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { return false }
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
adapter.showMenu(viewHolder.adapterPosition)
}
override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float {
return 0.1f
}
}
HistoryViewModel
class HistoryViewModel(private var historyListHandle: SavedStateHandle) : ViewModel() {
fun getHistoryList(): LiveData<MutableList<HistoryObject>> {
return historyListHandle.getLiveData(HISTORY_LIST_KEY)
}
fun setHistoryList(newHistoryList: MutableList<HistoryObject>) {
historyListHandle.set(HISTORY_LIST_KEY, newHistoryList)
}
companion object {
const val HISTORY_LIST_KEY = "MY_HISTORY_LIST"
}
}
Activity
class MainActivity : AppCompatActivity() {
private val historyViewModel: HistoryViewModel by lazy {
ViewModelProvider(this).get(HistoryViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
historyViewModel.setHistoryList(mutableListOf())
}
}
Thanks in advance. If this question is too broad I can try again and decompose it.
You shouldn't create new adapter every time you get an update of your history list. Keep using the same adapter, just update the items and call notifyDataSetChanged() to update the state (of course you can use different methods to notify about the insertion/deletion/etc, but make it work with notifyDataSetChanged() first).
I'm pretty sure this will fix the issue.
This is my RecyclerView Adaper
class RecyclerAdapter(private val recyclerList: List): RecyclerView.Adapter(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): mainRecyclerViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.quiz_item_recycler_view,
parent, false)
return mainRecyclerViewHolder(itemView)
}
override fun getItemCount() = recyclerList.size
override fun onBindViewHolder(holder: mainRecyclerViewHolder, position: Int) {
val currentItem = recyclerList[position]
holder.imageView.setImageResource(currentItem.imageResource)
holder.textView.text = currentItem.recyclerCardText
}
class mainRecyclerViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val imageView: ImageView = itemView.rec_image
val textView: TextView = itemView.text_view_1
}
}
And this is my data class
data class RecyclerItemMain(val imageResource: Int, val recyclerCardText: String, val button: Button)
Like this:
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.yourButton.setOnClickListener { v ->
// Here your start your other activity or navigate to another fragment
}
}
Remember that if you intent to send the user to diferent activities depending on the button, you have to create a when expression what will send the user to an activity depending on another value in the recyclerItem, for example the text of the item, in your case currentItem.recyclerCardText
Here is your full adapter, re-organized:
class RecyclerAdapter(recyclerList: List<CategorySectionIcon>) :
RecyclerView.Adapter<YourAdapter.CustomViewHolder>() {
private var recyclerList: List<CategorySectionIcon>? = recyclerList
inner class CustomViewHolder(
//Get a reference to the Views in our layout//
val myView: View
) : RecyclerView.ViewHolder(myView) {
var textView: TextView = myView.findViewById(R.id.your_text)
var imageView: ImageView = myView.findViewById(R.id.your_image)
var yourButton: Button = myView.findViewById(R.id.iv_category_imagen_icon)
}
override//Construct a RecyclerView.ViewHolder//
fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val itemView =
layoutInflater.inflate(R.layout.quiz_item_recycler_view, parent, false)
return CustomViewHolder(itemView)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.textView.text = recyclerList!![position].recyclerCardText
holder.imageView.setImageResource(recyclerList!![position].image)
holder.yourButton.setOnClickListener { v ->
// Here your start your other activity or navigate to another fragment
}
}
//Calculate the item count for the RecylerView//
override fun getItemCount(): Int {
return recyclerList!!.size
}
}
I'm new to Android development (and Kotlin).
I'm trying to implement a RecyclerView (which works fine) and when I click on a specific row it opens a new activity (Intent).
However, whenever I've press/click on one of the rows, I'm only able to get the value "-1" returned.
I've tried a number of different approaches (you should see the number of tabs in my browser).
This seems like it should be a fairly straightforward occurrence for something as common as a RecyclerView, but for whatever reason I'm unable to get it working.
Here is my RecyclerView Adapter file:
class PNHLePlayerAdapter (val players : ArrayList<PNHLePlayer>, val context: Context) : RecyclerView.Adapter<ViewHolder>() {
var onItemClick: ((Int)->Unit) = {}
// Gets the number of items in the list
override fun getItemCount(): Int {
return players.size
}
// Inflates the item views
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(context).inflate(
R.layout.pnhle_list_item,
parent,
false
)
val viewHolder = ViewHolder(itemView)
itemView.setOnClickListener {
onItemClick(viewHolder.adapterPosition)
}
return ViewHolder(itemView)
}
// Binds each item in the ArrayList to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvPlayerName?.text = players[position].Name
holder.tvPlayerRank?.text = position.toString()
holder.tvPNHLe?.text = players[position].PNHLe.toString()
holder.tvTeam?.text = players[position].Team
holder.ivLeague?.setImageResource(leagueImageID)
}
}
class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val linLayout = view.hor1LinearLayout
val ivTeam = view.teamImageView
val tvPlayerName = view.playerNameTextView
val tvPlayerRank = view.rankNumTextView
val tvPNHLe = view.pnhleTextView
val tvTeam = view.teamTextView
val ivLeague = view.leagueImageView
}
As you can see, there is a class property "onItemClick" which uses a lambda as the click callback.
I setOnClickListener in the onCreateViewHolder method after the view is inflated.
Next, in my Activity I add the list to my Adapter and set the call back.
However, every time I 'Toast' the position it is displayed as '-1'.
val adapter = PNHLePlayerAdapter(list, this)
adapter.onItemClick = { position ->
Toast.makeText(this, position.toString(),Toast.LENGTH_SHORT).show()
var intent = Intent(this, PlayerCardActivity::class.java)
//startActivity(intent)
}
rv_player_list.adapter = adapter
Perhaps I'm not thinking about this properly, but shouldn't the position represent the row number of the item out of the RecyclerView???
Ideally, I need to use the position so that I can obtain the correct item from the 'list' (ArrayList) so that I can pass information to my next Activity using the Intent
I found the issue.
Change this line in onCreateViewHolder:
return ViewHolder(itemView)
to this one:
return viewHolder
I would reorganize the adapter like this:
class PNHLePlayerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<Adapter.ViewHolder>() {
interface AdapterListener {
fun onItemSelected(position: Int?)
}
var players: List<Player> = listOf()
set(value) {
field = value
this.notifyDataSetChanged()
}
var listener: AdapterListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_car_selector, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount(): Int {
return brands.size
}
inner class ViewHolder(view: View): androidx.recyclerview.widget.RecyclerView.ViewHolder(view) {
private var position: Int? = null
private val baseView: LinearLayout? = view.findViewById(R.id.baseView) as LinearLayout?
...
init {
baseView?.setOnClickListener {
listener?.onManufacturerSelected(position)
}
}
fun bind(position: Int) {
this.position = position
...
}
}
}
And from your activity/fragment set the listener as adapter.listener = this, and implement the onItemSelected(position: Int?)
override fun onItemSelected(position: Int?) {
...
}
I have a problem with:
RecyclerView: No adapter attached; skipping layout
I can't define adapter in OnCreate because the list is not ready.
How I can define adapter in OnCreate? or what is a possible solution for resolve my problem?
In onCreate I did:
adapter = MyAdapter(this#MyActivity)
adapter.data = ArrayList()
Then later I just set adapter.date = xxx
In my adapter I have:
class MyAdapter(val activity: MyActivity) :
RecyclerView.Adapter<MyAdapter.BodyViewHolder>() {
var data: MutableList<MyModel>? = null
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemCount() = data?.size ?: 0
fun ViewGroup.inflate(#LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View {
return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BodyViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.my_layout, parent, false)
return BodyViewHolder(view)
}
override fun onBindViewHolder(holder: BodyViewHolder, position: Int) {
holder.bindValue(data?.get(position), activity)
}
class BodyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindValue(record: MyModel?, activity: MyActivity) {
record?.let {
itemView.mTextView.text = ....
}
}
}
}
Worth mentioning that the height of my recycler view is wrap_content
<android.support.v7.widget.RecyclerView
android:id="#+id/mRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
It's totally ok if you don't have data in onCreate. What you need to do is to define the adapter and bind it with your RecyclerView. Once you have data ready, add data to the list in adapter and notify it. Example as below
class MyAdapter: RecyclerView.Adapter<MyViewHolder>() {
private val data = mutableListOf<MyModel>()
override fun getItemCount() = data.count()
fun addData(data : MyModel) {
// add single data the list or call addAll() to add a group of data.
// just remember not to replace the variable
}
fun clearData() {
}
fun deleteData(id: Int) {
}
}
class adpCoba(val context: Context, val datalist:ArrayList<dataCoba>):RecyclerView.Adapter<adpCoba.MyViewHolder>(){
class MyViewHolder (itemView:View):RecyclerView.ViewHolder(itemView){
val text :TextView=itemView.findViewById(R.id.textcoba)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(context).inflate(R.layout.fetch_coba,parent,false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currntItem = datalist [position]
holder.text.text= currntItem.nama
}
override fun getItemCount(): Int {
return datalist.size
}
}
I am using recyclerview to display images in cardview. When I load images from the url. It loads well from first time but when It loads for second time it is not displayed properly.
How to fix it? Here is the code
override fun getItemCount(): Int {
dataList = FlickrDBOperation.dataArray
if (dataList!!.isEmpty())
return 0
Log.e("count",dataList!!.size.toString())
return dataList!!.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
var itemView: View = LayoutInflater.from(parent.context).inflate(R.layout.imagelist_row,parent,false)
var viewHolder: FlickrAdapter.Holder = FlickrAdapter.Holder(itemView)
return viewHolder
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.image_name.text = dataList!!.get(position).name
Picasso.with(context).load(dataList!!.get(position).preUrl).into(holder.row_image)
}
class Holder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
val row_image: ImageView
val image_name: TextView
init {
row_image = itemView!!.findViewById<ImageView>(R.id.row_image)
image_name = itemView!!.findViewById<TextView>(R.id.image_name)
}
}