RecyclerView's ViewHolders becoming unclickable - android

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.

Related

lateinit property recview has not been initialized

I have fragment which showing weather for 10 days with getting geolocation city and show exactly weather for this city for 10 days.
I have problem initialization with RecyclerView and Viewmodel.
Also I use Hilt to provide dependencies.
My goal is showing weather by location (already have permission) for 10 days.
import androidx.lifecycle.LiveData
import dagger.hilt.android.lifecycle.HiltViewModel
import db.entities.WeatherData
#HiltViewModel
interface ForecastViewmodel {
val dataforecast:LiveData<List<WeatherData>>
val city : LiveData<String>
val isRefreshing:LiveData<Boolean>
fun onRefresh()
}
#HiltViewModel
class ForecastViewmodelImpl(private val repository: ForecastRepository,
private val day: Day
) : ForecastViewmodel,
ViewModel(){
override val dataforecast = MutableLiveData<List<WeatherData>>()
override val city = MutableLiveData(day.city)
override val isRefreshing = MutableLiveData(true)
init {
loadForecast()
}
private fun loadForecast() {
isRefreshing.value = true
viewModelScope.launch {
try {
val data = repository.getForecast(day.days)
Timber.d("size is ${data}")
}catch (e:Exception){
Timber.e(e)
}
isRefreshing.value = false
}
}
override fun onRefresh() = loadForecast()
private fun Forecastday.toWeatherData() = WeatherData(
date = date,
temp = "${temp.roundToInt()}℃"
)
}
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.testtaskweatherappkevychsol.R
import db.entities.WeatherData
class WeatherRecView:RecyclerView.Adapter<WeatherRecView.WeatherHolder>() {
var listweather = emptyList<WeatherData>()
class WeatherHolder(view:View):RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): WeatherHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.daily_weather_layout,
parent,false)
return WeatherHolder(view)
}
override fun onBindViewHolder(
holder: WeatherHolder,
position: Int
) {
holder.itemView.findViewById<TextView>(R.id.Dateitem).text = listweather[position].date
holder.itemView.findViewById<TextView>(R.id.tempitem).text = listweather[position]
.temp.toString()
}
override fun getItemCount(): Int {
return listweather.size
}
fun addlistintoUI(list: List<WeatherData>){
listweather = list
}
}
data class WeatherData(
val temp:String,
val date:String
)
import RecyclerView.WeatherRecView
import Viewmodel.ForecastViewmodel
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.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.testtaskweatherappkevychsol.R
import com.example.testtaskweatherappkevychsol.databinding.FragmentWeatherBinding
import dagger.hilt.android.AndroidEntryPoint
#AndroidEntryPoint
class WeatherAtTheLifeMomentFragment : Fragment(R.layout.fragment_weather){
lateinit var recview:WeatherRecView
private var binding : FragmentWeatherBinding? = null
lateinit var viewmodel:ForecastViewmodel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentWeatherBinding.inflate(inflater,container,false)
this.binding = binding
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(binding){
this!!.refreshbt.setOnClickListener { viewmodel.isRefreshing }
containerlistweather.adapter = recview
containerlistweather.addItemDecoration(DividerItemDecoration(containerlistweather.context,
(containerlistweather
.layoutManager as LinearLayoutManager).orientation))
with(viewmodel){
dataforecast.observe(viewLifecycleOwner) { recview.listweather = it }
City.text = city.toString()
}
}
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
}
lateinit property recview has not been initialized
you are getting this issue as you are trying to access lateinit property recview without initialising it
in WeatherAtTheLifeMomentFragment
From
containerlistweather.adapter = recview
To
recview = WeatherRecView() // initialise recview variable by creating instance of your adapter
containerlistweather.layoutManager = LinearLayoutManager(context) // not sure you have set layout manager in your xml so just adding it in your code as it is necessary to use
containerlistweather.adapter = recview
Also you have create method addlistintoUI in WeatherRecView class so use it and make below mentioned changes to load your data
in WeatherAtTheLifeMomentFragment class
From
dataforecast.observe(viewLifecycleOwner) { recview.listweather = it }
To
dataforecast.observe(viewLifecycleOwner) { recview.addlistintoUI(it) }
in WeatherRecView class
From
fun addlistintoUI(list: List<WeatherData>){
listweather = list
}
To
fun addlistintoUI(list: List<WeatherData>){
listweather.addAll(list)
notifyDataSetChanged()
}
```

How to pass data from adapter to fragment?

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.

Android Kotlin - Navigate between fragments using textView in recyclerView

I would like to navigate between fragments using a textView inside a recyclerview.
Currently I am successfully navigating between fragments using the item, but I would like to go further and navigate using the individual textViews within an item.
The recyclerview is inflated from a local room database.
Below is my adapter
package com.benb.inventory
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.benb.inventory.data.Item
import com.benb.inventory.data.getFormattedPrice
import com.benb.inventory.databinding.ItemListItemBinding
class ItemListAdapter(private val onItemClicked: (Item) -> Unit) :
ListAdapter<Item, ItemListAdapter.ItemViewHolder>(DiffCallback) {
class ItemViewHolder(internal var binding: ItemListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
binding.apply {
itemName.text = item.itemName
itemPrice.text = item.getFormattedPrice()
itemShop.text = item.shop.toString()
itemShop.setOnClickListener{
val shopName = itemShop.text.toString()
Toast.makeText(root.context, "Clicked: ${item.shop}", Toast.LENGTH_SHORT ).show()
}
}
}
}
companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.itemName == newItem.itemName
}
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ItemListAdapter.ItemViewHolder {
return ItemViewHolder(ItemListItemBinding.inflate(LayoutInflater.from(parent.context)))
}
override fun onBindViewHolder(holder: ItemListAdapter.ItemViewHolder, position: Int) {
val current = getItem(position)
holder.itemView.setOnClickListener {
onItemClicked(current)
}
holder.binding.itemShop.setOnClickListener {
onItemClicked(current)
val shopName = current.shop
fun showShopList(shopName: String) {
val shopName = holder.binding.itemShop.text.toString()
val action = ItemListFragmentDirections.actionItemListFragmentToShopItemFragment(shopName)
}
}
holder.bind(current)
}
}
This is the fragment that contains the list
package com.benb.inventory
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.benb.inventory.databinding.ItemListFragmentBinding
import kotlinx.coroutines.InternalCoroutinesApi
#InternalCoroutinesApi
class ItemListFragment : Fragment() {
#InternalCoroutinesApi
private val viewModel: InventoryViewModel by activityViewModels {
InventoryViewModelFactory(
(activity?.application as InventoryApplication).database.itemDao()
)
}
private var _binding: ItemListFragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ItemListFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter = ItemListAdapter {
val action = ItemListFragmentDirections.actionItemListFragmentToItemDetailFragment(it.id)
this.findNavController().navigate(action)
}
binding.recyclerView.adapter = adapter
viewModel.allItems.observe(this.viewLifecycleOwner) {items ->
items.let {
adapter.submitList(it)
}
}
binding.itemShop.setOnClickListener{
val shop = binding.itemShop.text.toString()
val action = ItemListFragmentDirections.actionItemListFragmentToShopItemFragment(shop)
viewModel.retrieveShopItems(shop)
this.findNavController().navigate(action)
}
binding.recyclerView.layoutManager = LinearLayoutManager(this.context)
binding.floatingActionButton.setOnClickListener {
val action = ItemListFragmentDirections.actionItemListFragmentToAddItemFragment(
getString(R.string.add_fragment_title)
)
this.findNavController().navigate(action)
}
}
}
For clarity this is what the inflated recyclerview looks like, it can contain many items, I have only added one.
At the moment if I click anywhere on the item it takes you to a screen with more detail about the fragment.
[A screenshot of the recycler view][1]
I would like to be able to navigate to a specific fragment depending on the textView clicked.
For example one fragment that only contains other products from the same shop.
As you may notice, I have been able to add an onClickListener in the ViewHolder class, it creates a Toast.
I have not had success in using the same onClickListener to navigate.
Thank you very much.
P.S. Any other advice is welcome, particularly if anyone knows what the #internalcoroutinesAPI thing is about please tell me!
[1]: https://i.stack.imgur.com/k6Td3.png

How to add RecyclerView in a Fragment?

Currently, I am trying to implement RecyclerView inside of Fragment but I cannot find a way to display it.
Here is my MainActivity:
package com.example
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import com.example.projectdrivemark.R
import com.example.projectdrivemark.databinding.ActivityMainBinding
import com.example.recyclerView.MockDatabase.Companion.createMockData
import com.example.recyclerView.PostAdapter
import com.example.recyclerView.RecyclerViewFragment
import com.example.tempConverter.TempConverterFragment
import com.example.uploaderView.UploaderFragment
class MainActivity : AppCompatActivity(), PostAdapter.OnPostClickListener {
private lateinit var binding: ActivityMainBinding
val dummyList = createMockData()
val adapter = PostAdapter(dummyList, this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
title = "First Kotlin App"
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val recyclerView = RecyclerViewFragment()
val tempConverterView = TempConverterFragment()
// recyclerView.layoutManager = LinearLayoutManager(this)
val uploaderView = UploaderFragment(this)
setFragmentView(recyclerView)
binding.bottomNavBar.setOnNavigationItemSelectedListener {
when(it.itemId){
R.id.listView -> setFragmentView(recyclerView)
R.id.tempConverterView -> setFragmentView(tempConverterView)
R.id.videoUploaderView -> setFragmentView(uploaderView)
}
true
}
}
private fun setFragmentView(fragment: Fragment){
supportFragmentManager.beginTransaction().apply {
replace(R.id.main_fragment_view, fragment)
//Will return to previous page when tap "Back Button" on the phone
addToBackStack(null)
commit()
}
}
override fun onEditPost(position: Int){
val clickedPost = dummyList[position]
clickedPost.title = "Updated title"
clickedPost.body = "Updated body"
adapter.notifyItemChanged(position)
}
override fun onDeletePost(position: Int) {
dummyList.removeAt(position)
adapter.notifyItemRemoved(position)
}
fun celsiusFunction(view: View) {
val tempConverter = TempConverterFragment()
tempConverter.celsiusFunction(view)
}
fun farenheitFunction(view: View){
val fahrenheitConverter = TempConverterFragment()
fahrenheitConverter.farenheitFunction(view)
}
}
Here is my RecyclerFragment:
package com.example.recyclerView
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.example.MainActivity
import com.example.projectdrivemark.R
class RecyclerViewFragment: Fragment() {
var adapter: RecyclerView.Adapter<PostAdapter.PostViewHolder>? = null
var layoutManager: RecyclerView.LayoutManager? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreate(savedInstanceState)
val binding = inflater.inflate(R.layout.fragment_list, container, false)
return binding
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.apply{
layoutManager = LinearLayoutManager(activity)
adapter = PostAdapter(dummyData = ArrayList<Post>(), MainActivity())
}
}
}
Here is my PostAdapter code:
package com.example.recyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.MainActivity
import com.example.projectdrivemark.R
class PostAdapter(val dummyData: ArrayList<Post>, val myListener: MainActivity) : RecyclerView.Adapter<PostAdapter.PostViewHolder>() {
inner class PostViewHolder(postView: View) : RecyclerView.ViewHolder(postView), View.OnClickListener{
val iconImage: ImageView = postView.findViewById(R.id.icon_image_view)
val title: TextView = postView.findViewById(R.id.title)
val body: TextView = postView.findViewById(R.id.body)
val deleteIcon: ImageView = postView.findViewById(R.id.delete_post_image)
val editIcon: ImageView = postView.findViewById(R.id.edit_post_image)
init {
deleteIcon.setOnClickListener(this)
editIcon.setOnClickListener(this)
}
override fun onClick(v: View?){
val position = adapterPosition
if(v?.id == editIcon.id){
myListener.onEditPost(position)
}else{
myListener.onDeletePost(position)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
val postView = LayoutInflater.from(parent.context).inflate(R.layout.post, parent, false)
return PostViewHolder(postView)
}
override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
val currentPost = dummyData[position]
holder.iconImage.setImageResource(currentPost.image)
holder.title.text = currentPost.title
holder.body.text = currentPost.body
}
override fun getItemCount(): Int {
return dummyData.size
}
interface OnPostClickListener{
fun onEditPost(position: Int)
fun onDeletePost(position: Int)
}
}
If anyone saw something I miss, please do tell me because I am stuck at how to display the RecyclerView. Any help would be appreciate.
Edit:
Here is my MockDatabase:
package com.example.recyclerView
import com.example.projectdrivemark.R
class MockDatabase {
companion object{
fun createMockData(): ArrayList<Post>{
val list = ArrayList<Post>()
for(i in 0 until 20){
val imageToSelect = when (i % 3){
0 -> R.drawable.ic_baseline_account_balance
1 -> R.drawable.ic_baseline_account_circle
2 ->R.drawable.ic_baseline_ac_unit
else -> R.drawable.ic_baseline_access_alarms
}
list.add(
Post(
imageToSelect,
title = "Title post of $i",
body = "Title post of $i"
)
)
}
return list
}
}
}
Here is my Layout_list xml 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">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycleViewMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
tools:listitem="#layout/recycler_view_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
You need to cast view to RecyclerView to access RecyclerView properties inside apply, also you cannot create instances of Activity by yourself,
MainActivity() is wrong and won't work, instead use requireActivity() which will provide context of Activity to which Fragment is attached to
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewbyId<RecyclerView>(R.id.recycleViewMain).apply{
layoutManager = LinearLayoutManager(requireActivity())
adapter = PostAdapter(dummyData = ArrayList<Post>(), requireActivity() as OnPostClickListener)
}
}
Also, as #Necronet mentioned, add some mock data to see if it is actually rendering.
UPDATE
Instead of passing MainActivity to Adapter, pass OnPostClickListener
adapter = PostAdapter(dummyData = ArrayList<Post>(), requireActivity() as OnPostClickListener)
Adapter
class PostAdapter(val dummyData: ArrayList<Post>, val myListener: OnPostClickListener) : RecyclerView.Adapter<PostAdapter.PostViewHolder>(){
// you can use myListener to call methods
}
It's simple you are passing empty dummyData try using createMockData() instead. Also as a rule of thumb in Android, never ever instatiate an Activity yourself, if you need the reference from within a fragment you can use getActivity() method. So this:
adapter = PostAdapter(dummyData = ArrayList<Post>(), MainActivity())
Should be:
adapter = PostAdapter(dummyData = createMockData(), getActivity() as MainActivity)

getActivity() / context in a ViewHolder in Kotlin Android

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() }
}

Categories

Resources