I'm building a ViewHolder and Adapter for a Fragment and when I try to make an OnClick for the ViewHolder, none of the contexts I pass in work. There is no activity from getActivity() that I can use, and p0!!.context nor itemView.context work either. Where should I be getting my context from, and how do I reference it?
package com._________.criminalintent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
class CrimeListFragment: Fragment() {
private var mCrimeRecyclerView: RecyclerView? = null
private var mAdapter: CrimeAdapter? = null
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// fragment_crime_list.xml has a RecyclerView element = crime_recycler_view
// inflate the fragment into the activity
val view = inflater!!.inflate(R.layout.fragment_crime_list, container, false)
// grab the recyclerView and give it a required layoutManager
mCrimeRecyclerView = view.findViewById(R.id.crime_recycler_view)
mCrimeRecyclerView!!.layoutManager = LinearLayoutManager(activity)
updateUI()
return view
}
private fun updateUI() {
val crimeLab = CrimeLab.get(activity)
val crimes = crimeLab.getCrimes()
mAdapter = CrimeAdapter(crimes)
// Connect the adapter to the recyclerView
mCrimeRecyclerView!!.adapter = mAdapter
}
/**
* in Kotlin, we must give the view passed into the constructor directly
* as a substitute for a super() call
*
* create a ViewHolder that holders the crime list item's view
*
* super(itemView) = super(inflater!!.inflate(R.layout.list_item_crime, parent, false))
* MUST give it the direct value in Kotlin
*/
private class CrimeHolder(inflater: LayoutInflater?, parent: ViewGroup):
RecyclerView.ViewHolder(inflater!!.inflate(R.layout.list_item_crime, parent, false)),
View.OnClickListener {
private var mCrime: Crime? = null
/**
* When given a crime, this CrimeHolder will update the title and date for this Crime
*/
fun bind(crime: Crime) {
mCrime = crime
val titleTextView = itemView.findViewById<TextView>(R.id.crime_title)
val dateTextView = itemView.findViewById<TextView>(R.id.crime_date)
titleTextView.text = mCrime!!.mTitle
dateTextView.text = mCrime!!.mDate.toString()
}
override fun onClick(p0: View?) {
Toast.makeText(WHAT_TO_PUT_HERE, "${mCrime!!.mTitle} clicked!", Toast.LENGTH_SHORT / 2)
.show()
}
}
private class CrimeAdapter(private var mCrimes: MutableList<Crime>):
RecyclerView.Adapter<CrimeHolder>() {
/**
* - Calls our CrimeHolder to make our custom ViewHolders
* - Called by RecyclerView when it needs a new view to display
* - Gets the layoutInflater from the ViewGroup and returns a CrimeHolder of it
*/
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): CrimeHolder
= CrimeHolder(LayoutInflater.from(parent!!.context), parent)
/**
* Bind the crime (data) to the CrimeHolder
*/
override fun onBindViewHolder(holder: CrimeHolder?, position: Int) {
holder!!.bind(mCrimes[position])
}
/**
* Sees how many items are in the RecyclerView that need to be shown
*/
override fun getItemCount(): Int = mCrimes.size
}
}
In your implementation you can safely use Context from View provided to your OnClickListener
override fun onClick(p0: View) {
Toast.makeText(p0.context, "${mCrime!!.mTitle} clicked!", Toast.LENGTH_SHORT / 2)
.show()
}
Just remember to set onclick:
fun bind(crime: Crime) {
mCrime = crime
val titleTextView = itemView.findViewById<TextView>(R.id.crime_title)
val dateTextView = itemView.findViewById<TextView>(R.id.crime_date)
titleTextView.text = mCrime!!.mTitle
dateTextView.text = mCrime!!.mDate.toString()
itemView.setOnClickListener(this)
}
Moreover all Kotlin classes are nested (static) by default. So your private class CrimeHolder is equivalent to private static class CrimeHolder in Java. That's why you don't have access to getActivity() from within CrimeHolder
Use itemView.context property inside your holder.
Edit:
The reason why your onClick doesn't "work" (is not called) is because you haven't registered the onClickListener, for example:
itemView.setOnClickListener(this)
inside your holder init or bind.
itemView.setOnClickListener {
Toast.makeText(itemView.context, "Item is clicked $position", Toast.LENGTH_SHORT).show() }
}
Related
I've been trying to pass data(the email and phone of a user) from my adapter to my fragment. From what I've read online I should use a interface for this but I cant the data into my fragment still. Can anyone explain in steps how I should add a interface and how to put data into my interface from my adapter so I can call it in my fragment. Or is there another way to pass data from my adapter to my fragment. Below are my adapter and my fragment.
Adapter:
package ie.wit.savvytutor.adapters
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import ie.wit.savvytutor.R
import ie.wit.savvytutor.activity.MainActivity
import ie.wit.savvytutor.fragments.ViewChatFragment
import ie.wit.savvytutor.models.UserModel
class UserAdapter(private val userList: ArrayList<UserModel>, val context: Context) :
RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val itemView =
LayoutInflater.from(parent.context).inflate(R.layout.user_layout, parent, false)
return UserViewHolder(itemView)
}
class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val username: TextView = itemView.findViewById(R.id.userNameView)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int, ) {
val currentItem = userList[position]
holder.username.text = currentItem.email
holder.itemView.setOnClickListener {
println(currentItem)
val optionsFrag = ViewChatFragment()
(context as MainActivity).getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, optionsFrag, "OptionsFragment").addToBackStack(
null
)
.commit()
}
}
override fun getItemCount(): Int {
return userList.size
}
}
Fragment
package ie.wit.savvytutor.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.Nullable
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.*
import ie.wit.savvytutor.R
import ie.wit.savvytutor.adapters.UserAdapter
import ie.wit.savvytutor.models.UserModel
class TutorChatFragment : Fragment() {
private lateinit var userRecyclerView: RecyclerView
private lateinit var userArrayList: ArrayList<UserModel>
private lateinit var dbRef: DatabaseReference
private lateinit var mAuth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dbRef = FirebaseDatabase.getInstance("DATABASE LINK").getReference("Users").ref
mAuth = FirebaseAuth.getInstance()
}
#Nullable
override fun onCreateView(
inflater: LayoutInflater,
#Nullable container: ViewGroup?,
#Nullable savedInstanceState: Bundle?
): View {
//inflate the fragment layout
val root = inflater.inflate(R.layout.tutor_chat_fragment, container, false)
userRecyclerView = root.findViewById(R.id.userListView)
userRecyclerView.layoutManager = LinearLayoutManager(context)
userRecyclerView.setHasFixedSize(true)
userArrayList = arrayListOf<UserModel>()
getUser()
return root
}
private fun getUser() {
userArrayList.clear()
dbRef.addValueEventListener(object: ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
for (postSnapshot in snapshot.children) {
val currentUser = postSnapshot.getValue(UserModel::class.java)
//BUG FIX 1.26.13
val email = currentUser?.email
if (email != null) {
userArrayList.add(currentUser)
}
userRecyclerView.adapter?.notifyDataSetChanged()
userRecyclerView.adapter = context?.let { UserAdapter(userArrayList, it) }
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
}
If you want to use an interface, you just need to define one with a function to receive your data, make the fragment implement it, then pass the fragment to the adapter as an implementation of that interface:
data class UserData(val email: String, val phone: String)
class UserAdapter(
private val userList: ArrayList<UserModel>,
val context: Context,
val handler: UserAdapter.Callbacks // added this here, so you're passing it in at construction
) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
...
private fun doWhatever(email: String, phone: String) {
// pass the data to the handler (which will probably be your Fragment)
handler.handleUserData(UserData(email, phone))
}
// nested inside the UserAdapter class to keep things tidy
interface Callbacks {
fun handleUserData(data: UserData)
}
}
Then in the Fragment:
// add the Callbacks interface type
class TutorChatFragment : Fragment(), UserAdapter.Callbacks {
override fun onCreateView(
inflater: LayoutInflater,
#Nullable container: ViewGroup?,
#Nullable savedInstanceState: Bundle?
): View {
...
userRecyclerView.layoutManager = LinearLayoutManager(context)
// set up the adapter here, passing this fragment as the Callbacks handler
userRecyclerView.adapter = UserAdapter(userArrayList, context, this)
...
}
// interface implementation
override fun handleUserData(data: UserData) {
// whatever
}
}
And that's it. You're not hardcoding a dependency on that particular Fragment type, just the interface, and this fragment implements it so it can pass itself.
A more Kotliny way to do it is to ignore interfaces and just pass a function instead
class UserAdapter(
private val userList: ArrayList<UserModel>,
val context: Context,
val handler: (UserData) -> Unit // passing a function that takes a UserData instead
) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
...
private fun doWhatever(email: String, phone: String) {
// call the handler function with your data (you can write handler.invoke() if you prefer)
handler(UserData(email, phone))
}
}
// no interface this time
class TutorChatFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
#Nullable container: ViewGroup?,
#Nullable savedInstanceState: Bundle?
): View {
...
userRecyclerView.layoutManager = LinearLayoutManager(context)
// pass in a handler function
userRecyclerView.adapter = UserAdapter(userArrayList, context) { userData ->
handleUserData(userData)
}
// or if you're just passing it to that function down there,
// you could do UserAdapter(userArrayList, context, ::handleUserData)
// and pass the function reference
...
}
// might be convenient to still do this in its own function
private fun handleUserData(data: UserData) {
// whatever
}
}
Ideally you should be doing what I've done there - create the adapter once during setup, and have a function on it that allows you to update it. Your code creates a new one each time you get data. You do this the same way in both though
Your other option is using a view model that the adapter and fragment both have access to, but this is how you do the interface/callback approach
Actually there is one very easy way to get data from your adapter in to your fragment or activity. It is called using Higher Order Functions.
In your adapter
Add higher order function in your adapter.
class UserAdapter(private val userList: ArrayList<UserModel>, val context: Context) :
RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
//your rest of the adapter's code
private var onItemClickListener:((UserModel)->Unit)? = null
fun setOnItemClickListener(listener: (UserModel)->Unit) {
onItemClickListener = listener
}
}
In Your UserViewHolder
val rootView = itemView.rootView
In Your onBindViewHolder
set a click listener on rootView
holder.rootView.setOnClickListener {
onItemClickListener?.let{
it(currentItem)
}
}
In Your Fragment
//create the instance of UserAdapter
userAdapter.setOnItemClickListener {
//here you have your UserModel in your fragment, do whatever you want to with it
}
And, a suggestion in the last. Start using ViewBinding, it will save you from a lots of hectic work.
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
Can someone help me on how to transfer data from one recyclerview fragment to another recyclerview fragment?
This is my newly created CartRecyclerAdapter.kt for my cart recyclerview from a fragment. The main idea of SubmitItem() is to accept the selected item in the Itemrecyclerview.
package com.example.karawapplication
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.example.karawapplication.models.ItemPost
import kotlinx.android.synthetic.main.layout_item_cart_list.view.*
import kotlinx.android.synthetic.main.layout_item_list_item.view.*
class CartRecyclerAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var Items : List<ItemPost> = emptyList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ItemRecyclerAdapter.ItemViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.layout_item_cart_list, parent, false)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder)
{
is ItemRecyclerAdapter.ItemViewHolder ->
{
holder.bind(Items.get(position))
}
}
}
override fun getItemCount(): Int {
return Items.size
}
fun SubmitItem(Item : ItemPost)
{
Items.toMutableList().add(Item)
}
class ItemViewHolder constructor(
Itemview : View
): RecyclerView.ViewHolder(Itemview)
{
val ItemImage : ImageView = Itemview.Item_image_cart
val ItemTitle : TextView = Itemview.Item_title_cart
val ItemCategory: TextView = Itemview.Item_category_cart
val ItemPrice : TextView = Itemview.Item_price_cart
fun bind (itempost : ItemPost)
{
ItemTitle.setText(itempost.Itemtitle)
ItemCategory.setText(itempost.ItemCategory)
ItemPrice.setText(itempost.ItemPrice)
val requestOptions = RequestOptions()
.placeholder(R.drawable.ic_baseline_brush_24)
.error(R.drawable.ic_baseline_brush_24)
Glide.with(itemView.getContext())
.applyDefaultRequestOptions(requestOptions)
.load(itempost.image)
.into(ItemImage)
}
}
}
This is where I use the SubmitItem() in my ItemRecyclerAdapter.kt which contains items of my shop app and displayed from another fragment. CartAdapter is the adapter of the cart that I recently created in order to access the function SubmitItem().
ItemButton.setOnClickListener()
{
Toast.makeText(itemView.context, "${itempost.image}, ${itempost.Itemtitle}, ${itempost.ItemPrice}", Toast.LENGTH_SHORT).show()
CartAdapter.SubmitItem(ItemPost(itempost.image,itempost.Itemtitle,itempost.ItemCategory,itempost.ItemPrice))
}
This is my code for my fragments
ShopFragment.kt contains the recyclerview of my items.
package com.example.karawapplication.fragments
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import com.android.volley.Request
import com.android.volley.VolleyError
import com.android.volley.toolbox.JsonArrayRequest
import com.android.volley.toolbox.Volley
import com.example.karawapplication.ItemRecyclerAdapter
import com.example.karawapplication.R
import com.example.karawapplication.models.ItemPost
import kotlinx.android.synthetic.main.fragment_shop.*
import org.json.JSONException
import org.json.JSONObject
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [Shop.newInstance] factory method to
* create an instance of this fragment.
*/
class Shop : Fragment(){
// TODO: Rename and change types of parameters
private lateinit var ItemAdapter : ItemRecyclerAdapter
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_shop, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initRecyclerView()
addDataSet()
}
// CUSTOM FUNCTIONS
private fun addDataSet()
{
createDataset {
list -> ItemAdapter.SubmitList(list)
}
}
private fun initRecyclerView()
{
ShopRecycleView.layoutManager = LinearLayoutManager(activity)
ItemAdapter = ItemRecyclerAdapter()
ShopRecycleView.adapter = ItemAdapter
//Toast.makeText(context, "Recycler Trigger", Toast.LENGTH_SHORT).show()
}
// https://tutorials.eu/json-parsing-and-how-to-use-gson-in-android/
// Generates the data for the recycleview
fun createDataset(onSuccess: (List<ItemPost>) -> Unit){
val url = "http://api.karawcraftventure.com/item"
// LIST DATA VARIABLE FOR RECYCLEVIEW
val list = ArrayList<ItemPost>()
// VOLLEY API REQUEST
val Queue = Volley.newRequestQueue(activity)
val jsonObject = JsonArrayRequest(
Request.Method.GET,url,null,
{response ->
try
{
for (i in 0 until response.length())
{
val item : JSONObject = response.getJSONObject(i)
val API_Image : String = item.getString("product_url_image")
val API_ItemName : String = item.getString("product_name")
val API_Price : String = item.getString("product_price")
val API_Category : String = item.getString("product_category")
// Toast Notif if data is extracted or not
//Toast.makeText(context, "$API_ItemName - $API_Price - $API_Category", Toast.LENGTH_SHORT).show()
list.add(ItemPost(API_Image, API_ItemName, API_Category, API_Price))
}
onSuccess(list)
}
catch (e: JSONException)
{
e.printStackTrace()
}
},
{ error: VolleyError? -> Toast.makeText(context, error?.message.toString(), Toast.LENGTH_SHORT).show()
}
)
Queue.add(jsonObject)
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment Shop.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
Shop().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
Cart.kt fragment on the other hand, contains my shopping cart.
package com.example.karawapplication.fragments
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.karawapplication.CartRecyclerAdapter
import com.example.karawapplication.ItemRecyclerAdapter
import com.example.karawapplication.R
import kotlinx.android.synthetic.main.fragment_cart.*
import kotlinx.android.synthetic.main.fragment_shop.*
import kotlinx.android.synthetic.main.fragment_shop.ShopRecycleView
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [Cart.newInstance] factory method to
* create an instance of this fragment.
*/
class Cart : Fragment() {
// TODO: Rename and change types of parameters
private lateinit var ItemAdapter : CartRecyclerAdapter
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initRecyclerView()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_cart, container, false)
}
private fun initRecyclerView()
{
Cart_RecyclerView.layoutManager = LinearLayoutManager(activity)
ItemAdapter = CartRecyclerAdapter()
Cart_RecyclerView.adapter = ItemAdapter
//Toast.makeText(context, "Recycler Trigger", Toast.LENGTH_SHORT).show()
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment Cart.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
Cart().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
The code has no errors, but it does not show any output in my cart recyclerview fragment.
(I'm going to follow the "starts with a lowercase letter" convention for variables and functions here, because I'm tired and it's less confusing)
Your submitItem function just creates a new copy of items (items.toMutableList()) and adds to that. Then it's immediately discarded when you exit the function.
All your adapter code that handles your data (getItemCount, onBindViewHolder) references items, so you need to update that instead, and let the adapter know it's changed:
fun submitItem(item: ItemPost) {
// plus creates a copy, but adds the new item without making the result mutable
items = items.plus(item)
// we also need to tell the adapter that the data has changed, so it can
// update - there are more efficient calls (you'll probably get a lint
// warning telling you to use one) but this is the simplest
notifyDataSetChanged()
}
This is just making your adapter update correctly - I'm assuming you've got your communication between the two fragments set up properly (communicating through the parent activity, passing one fragment's adapter into the other, using a view model). Have a read of this if you need to: Communicating with fragments
I am trying to display items on a recyclerview but it says
"No adapter attached; skipping layout"
.
I can't figure out the error. I tried with Activities instead of Fragements and it works super well. I am not yet so familiar with Framents. Kindly help.
RecyclerAdapter
package com.manzugerald.shukuruyesu.adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.manzugerald.shukuruyesu.HymnDetailActivity
import com.manzugerald.shukuruyesu.R
import com.manzugerald.shukuruyesu.model.Hymns
class HymnsItemAdapter(
val context: Context,
val dataset: List<Hymns>
) : RecyclerView.Adapter<HymnsItemAdapter.ItemViewHolder>() {
//provide a reference to the views for each data item (in this case there is only one item)
//Complex data items may need more than one view per item, and
//you provide access to all the views for a data item in a view holder
//Each data item is just an Affirmation object
inner class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
//val hymn_imageView: ImageView =view.findViewById(R.id.item_image)
val hymn_title: TextView = view.findViewById(R.id.textView_hymn_title)
val hymn_language: TextView = view.findViewById(R.id.textView_hymn_language)
val hymn_number: TextView = view.findViewById(R.id.textView_hymn_number)
var hymn_detail: TextView = view.findViewById(R.id.textView_hymn_text)
//This has been unused. I will try to see how best to use it in the future
var hymnPosition = 0
/**
* //So as the contents of the views displayed are clickable, append the setOnClickListener to the view holder rather than individual views
* An explicit intent is used for exchanging information between two or more fragements or activities (screen)
* The information to be carried to the next screen is passed as a key-value pair
* The key is retrieved in the receiving activity or fragment
* Then the data the key carries is gotten and assigned to the views so as to be viewed
*/
init {
view.setOnClickListener {
val intent = Intent(context,HymnDetailActivity::class.java)
val message = hymn_number.text.toString()
val message_two = hymn_language.text.toString()
val message_three = hymn_title.text.toString()
val message_four = hymn_detail.text.toString()
intent.putExtra("key_one", message)
intent.putExtra("key_two",message_two)
intent.putExtra("key_three",message_three)
intent.putExtra("key_four",message_four)
context.startActivity(intent)
}
}
}
/**
* Create new views (invoked by the layout manager)
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val adapterLayout = LayoutInflater.from(parent.context).inflate(R.layout.list_item_hymn_list, parent, false)
return ItemViewHolder(adapterLayout)
}
/**
* Replace the contents of a view (invoked by the layout manager)
*/
/**
* Binds or attaches data to the views.
* Does this by interacting with the itemViewHolder
*/
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
//holder.hymn_imageView.setImageResource(item.imageResourceId)
holder.hymn_title.text = context.resources.getString(item.titleStringResourceId)
holder.hymn_language.text = context.resources.getString(item.languageStringResourceId)
holder.hymn_number.text = "Song Number: " + context.resources.getString(item.numberStringResourceId)
holder.hymn_detail.text = context.resources.getString(item.detailStringResourceId)
holder.hymnPosition = position
holder.hymn_detail.isVisible=false
}
/**
* Return the size of the dataset (invoked by the layout manager)
*/
//Takes not of the size of the data, number of rows so that binding can be done with ease
override fun getItemCount(): Int = dataset.size
}
The data source:
package com.manzugerald.shukuruyesu.data
import com.manzugerald.shukuruyesu.R
import com.manzugerald.shukuruyesu.model.Hymns
class HymnsDataSource {
//Gets the data from the string resources
fun loadHymns():List<Hymns>{
return listOf<Hymns>(
Hymns(R.string.affirmation1,R.string.affirmationNum1,R.string.affirmationLang1,R.string.affirmationDetail1),
Hymns(R.string.affirmation2,R.string.affirmationNum2,R.string.affirmationLang2,R.string.affirmationDetail2),
Hymns(R.string.affirmation3,R.string.affirmationNum3,R.string.affirmationLang3,R.string.affirmationDetail3),
Hymns(R.string.affirmation4,R.string.affirmationNum4,R.string.affirmationLang4,R.string.affirmationDetail4),
Hymns(R.string.affirmation5,R.string.affirmationNum5,R.string.affirmationLang5,R.string.affirmationDetail5),
Hymns(R.string.affirmation6,R.string.affirmationNum6,R.string.affirmationLang6,R.string.affirmationDetail6),
Hymns(R.string.affirmation7,R.string.affirmationNum7,R.string.affirmationLang7,R.string.affirmationDetail7),
Hymns(R.string.affirmation8,R.string.affirmationNum8,R.string.affirmationLang8,R.string.affirmationDetail8),
Hymns(R.string.affirmation9,R.string.affirmationNum9,R.string.affirmationLang9,R.string.affirmationDetail9),
Hymns(R.string.affirmation10,R.string.affirmationNum10,R.string.affirmationLang10,R.string.affirmationDetail10)
)
}
}
The Data class:
package com.manzugerald.shukuruyesu.model
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
data class Hymns(
#StringRes var titleStringResourceId: Int,
#StringRes var numberStringResourceId: Int,
#StringRes var languageStringResourceId: Int,
#StringRes var detailStringResourceId: Int
// #DrawableRes val imageResourceId: Int
)
The Fragment to display the recyclerview:
package com.manzugerald.shukuruyesu
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.manzugerald.shukuruyesu.data.HymnsDataSource
import com.manzugerald.shukuruyesu.databinding.FragmentHymnListBinding
import com.manzugerald.shukuruyesu.adapter.HymnsItemAdapter
import com.manzugerald.shukuruyesu.model.Hymns
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [HymnListFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class HymnListFragment : Fragment() {
// TODO: Rename and change types of parameters
//enable and refrence binding
private var _binding: FragmentHymnListBinding? = null
private val binding get() = _binding!!
//property for the recycler view
private lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
//To inflate the layout, call teh onCreatView Method
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentHymnListBinding.inflate(inflater,container,false)
val view = binding.root
return view
}
//Bind the views in onViewCreated
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val myDataSource = HymnsDataSource().loadHymns()
recyclerView = binding.recyclerView
recyclerView.apply {
layoutManager=LinearLayoutManager(requireContext())
adapter= HymnsItemAdapter(dataset = myDataSource, context = requireContext())
adapter= HymnsItemAdapter(context,myDataSource)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding=null
}
}
HymnListFragment Layout File:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".HymnListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_View"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="#layout/list_item_hymn_list"/>
</androidx.constraintlayout.widget.ConstraintLayout>
I got the solution to the question...
Neither the Adapter nor the Fragments had a problem.
The problem was in my MainActivity.kt file, which I didn't upload here...
The error came about when I tried changing from activities to Fragments....
Initially, I forgot to take off or comment the inflation of the layouts (Layouts are inflated in the respective Fragments, not the main activity).
For illustration purposes, I will comment the said error in the code.
My bad!
Main Activity class:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(R.layout.activity_main)
}
}
Prehisrory
I have a list of stocks (some objects) from RoomDB. Each one of them have "symbol", "name",
"price" and what's most important, "isFavourite" fields.
I made a ViewPager2 with two Fragments containing RecyclerView (actually there are just two instances of one class StocksFragment - one for all stocks, one for only favourite stocks). Each stock in RecyclerView is connected to the repository through Obsrver (data changes => stock's ViewHolder changes). Also each ViewHolder has own checkBox that changes "isFavourite" Stock field through StockListViewModel that calls StockRepository, that works directly with roomDB (with kotlin coroutines - sth like
fun getStocks(): LiveData<List<Stock>> = runBlocking{ stockDao.getStocks() })
Problem
When i click the same checkBox several times in a relatively small amount of time, all RecyclerView's ViewHolders become unclickable (neither the delete button nor the checkbox works). But i still can scroll RecyclerView How can i fix that?
I think i am doing something very inefficient but i dont know what.
Here is my StocksFragment code:
package com.nikitakrapo.android.happystocks
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class StocksFragment(var stockListType: StockListType) : Fragment() {
private val stockListViewModel: StockListViewModel by lazy{
ViewModelProvider(this).get(StockListViewModel::class.java)
}
private lateinit var stocksRecyclerView: RecyclerView
private var adapter: StocksAdapter? = StocksAdapter()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_stocks, container, false)
stocksRecyclerView = view.findViewById(R.id.recycler_view)
stocksRecyclerView.layoutManager = LinearLayoutManager(context)
stocksRecyclerView.setHasFixedSize(true)
stocksRecyclerView.adapter = adapter
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var stocks = if (stockListType == StockListType.favouriteStocksList)
stockListViewModel.favStockListLiveData
else stockListViewModel.stockListLiveData
stocks.observe(
viewLifecycleOwner,
{ stocks ->
stocks?.let {
adapter?.setStocks(stocks)
}
}
)
}
private inner class StockHolder(view: View) : RecyclerView.ViewHolder(view){
private lateinit var stock: Stock
val symbolTextView: TextView = itemView.findViewById(R.id.stock_symbol)
private val nameTextView: TextView = itemView.findViewById(R.id.stock_name)
private val priceTextView: TextView = itemView.findViewById(R.id.stock_price)
private val stockImageView: ImageView = itemView.findViewById(R.id.stock_image)
val stockDeleteButton: Button = itemView.findViewById(R.id.stock_delete)
val favouriteCheckBox: CheckBox = itemView.findViewById(R.id.is_favourite)
fun bind(stock: Stock, holder: StockHolder) {
this.stock = stock
symbolTextView.text = this.stock.symbol
nameTextView.text = this.stock.name
priceTextView.text = "$" + this.stock.priceUSD.toString()
favouriteCheckBox.isChecked = this.stock.isFavourite
holder.stockDeleteButton.setOnClickListener {
stockListViewModel.deleteStock(stock)
}
holder.favouriteCheckBox.setOnCheckedChangeListener { buttonView, isChecked ->
stockListViewModel.updateFavourite(holder.symbolTextView.text.toString(), isChecked)
}
}
}
private inner class StocksAdapter
: RecyclerView.Adapter<StockHolder>() {
private var stockList: List<Stock> = emptyList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
: StockHolder {
val view = layoutInflater.inflate(R.layout.list_item_stock, parent, false)
return StockHolder(view)
}
override fun onBindViewHolder(holder: StockHolder, position: Int) {
val stock = stockList[position]
holder.bind(stock, holder)
}
public fun setStocks(stockList: List<Stock>){
this.stockList = stockList
notifyDataSetChanged()
}
override fun getItemCount() = stockList.size
}
}
Try OnClickListener instead
holder.favouriteCheckBox.setOnClickListener {
if ((it as CompoundButton).isChecked) {
stockListViewModel.updateFavourite(holder.symbolTextView.text.toString(), (it as CompoundButton).isChecked)
}
}
And provide stockListViewModel.updateFavourite code block. Is the problem reproduced when this line is commented out?
Calling stockListViewModel.deleteStock(stock) and stockListViewModel.updateFavourite(holder.symbolTextView.text.toString(), isChecked) in background thread like
withContext(Dispatchers.IO){stockListViewModel.deleteStock(stock)} might resolve the issue.