I am new to alert dialogs and was hoping somebody could help me with this. I want to develop a single choice alert dialog and have it show in a recyclerview textview along side an incremental counter.
I have searched all types of documentation but all I can find is how to display the single choice item in either a Toast or a single text view.
I know the code I have is incorrect, but after numerous other attempts, this is the closest I got to getting the result I am seeking. I was able to get it to set the most recent choice but then the other choices change into what look like memory allocations after the button is pressed.
Screenshot:
Here is my code:
Main Activity (I realize that tv_choice.setText(multiItems[i]) is part of the problem it in my dialogAlert(). This is what I need help with.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val itemsList = generateItemsList()
private val adapter = MyAdapter(itemsList)
var count = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(false)
binding.button.setOnClickListener {
addItemsToRecyclerView()
}
}
private fun generateItemsList(): ArrayList<Items> {
return ArrayList()
}
fun addItemsToRecyclerView() {
val addItems = Items(getCount(), "Your Choice Is:", dialogAlert())
itemsList.add(addItems)
adapter.notifyItemInserted(itemsList.size - 1)
}
private fun getCount(): String {
count += 1
return count.toString()
}
fun dialogAlert(): String {
val multiItems = arrayOf("Item 1", "Item 2", "Item 3")
val singleChoice = AlertDialog.Builder(this)
.setTitle("Choose one:")
.setSingleChoiceItems(multiItems, 1) { dialogInterface, i ->
tv_choice.setText(multiItems[i])
}
.setPositiveButton("ok") { _, _ ->
}
.create()
singleChoice.show()
val singleChoiceString = singleChoice.toString()
return singleChoiceString
}
}
The Adapter:
class MyAdapter(private val rvDisplay: MutableList<Items>) : RecyclerView
.Adapter<MyAdapter.AdapterViewHolder>(){
class AdapterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val textView1: TextView = itemView.findViewById(R.id.tv_count)
val textView2: TextView = itemView.findViewById(R.id.tv_choice_string)
val textView3: TextView = itemView.findViewById(R.id.tv_choice)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AdapterViewHolder {
val myItemView = LayoutInflater.from(parent.context).inflate(
R.layout.rv_items,
parent, false
)
return AdapterViewHolder(myItemView)
}
override fun onBindViewHolder(holder: MyAdapter.AdapterViewHolder, position: Int) {
val currentDisplay = rvDisplay[position]
holder.itemView.apply {
holder.textView1.text = currentDisplay.count
holder.textView2.text = currentDisplay.choiceString
holder.textView3.text = currentDisplay.choice
}
}
override fun getItemCount() = rvDisplay.size
}
While you tried to add the dialog's selected value to the recycler view, what you actually did was adding the dialogAlert() returned value to the recycler view.
Instead of "adding" an item when the button is clicked, you should add the item once the dialog is closed. So first present the dialog:
binding.button.setOnClickListener {
dialogAlert()
}
Remove the return value from dialogAlert() method and then, when selecting an option from the dialog, add it to the recycler view:
fun dialogAlert() {
val multiItems = arrayOf("Item 1", "Item 2", "Item 3")
val singleChoice = AlertDialog.Builder(this)
.setTitle("Choose one:")
.setSingleChoiceItems(multiItems, 1) { dialogInterface, i ->
addItemsToRecyclerView(multiItems[i])
}
.create()
singleChoice.show()
}
Change the method to receive a String (your item):
fun addItemsToRecyclerView(item: String) {
val addItems = Items(getCount(), "Your Choice Is:", item)
itemsList.add(addItems)
adapter.notifyItemInserted(itemsList.size - 1)
}
Note that I did not run this code so it might need some adjustments.
Related
I want to add editing tasks through dialogs in my app. The thing is I can't initialize viewHolder to access the bindingAdapterPosition. I need it to pass the data and update the viewModel. Tried adding it in the constructor - didn't work. I know I have to initialize the viewHolder, but don't know how.
RecyclerviewFragment.kt:
class RecyclerviewFragment : Fragment() {
private lateinit var mUserViewModel: UserViewModel
private lateinit var viewHolder: ViewHolder
private lateinit var adapter: ListAdapter
private var _binding: FragmentRecyclerviewBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
_binding = FragmentRecyclerviewBinding.inflate(inflater, container, false)
mUserViewModel = ViewModelProvider(this)[UserViewModel::class.java]
adapter = ListAdapter{showUpdateDialog()}
val adapter = ListAdapter{showUpdateDialog()}
val recyclerView = binding.recyclerView
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
// Creates a controller responsible for swiping and moving the views in recyclerview
val itemTouchController = ItemTouchHelper(
object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: ViewHolder, target: ViewHolder,
): Boolean {
// Move specific item from "fromPos" to "toPos" in recyclerview adapter
val fromPos = viewHolder.bindingAdapterPosition
val toPos = target.bindingAdapterPosition
adapter.notifyItemMoved(fromPos, toPos)
return true // true if moved, false otherwise
}
override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
mUserViewModel.deleteUser(adapter.getTaskPosition(viewHolder.bindingAdapterPosition))
Toast.makeText(context, "Task deleted", Toast.LENGTH_SHORT).show()
adapter.notifyItemRemoved(viewHolder.bindingAdapterPosition)
}
})
itemTouchController.attachToRecyclerView(binding.recyclerView)
mUserViewModel.readAllData.observe(viewLifecycleOwner) { user ->
adapter.setData(user)
}
return binding.root
}
private fun updateItemInDatabase(dialog: DialogInterface) {
val editText = (dialog as AlertDialog).findViewById<EditText>(R.id.editTextDialog)
val task = editText?.text.toString()
if(inputCheck(task)) {
// Update an entity
mUserViewModel.updateUser(adapter.getTaskPosition(viewHolder.bindingAdapterPosition))
Toast.makeText(context, "Task updated", Toast.LENGTH_SHORT).show()
}
else {
Toast.makeText(context, "Please fill out required fields", Toast.LENGTH_SHORT).show()
}
}
private fun inputCheck(task: String): Boolean {
return !(TextUtils.isEmpty(task))
}
private fun showUpdateDialog() {
MaterialAlertDialogBuilder(requireContext())
.setView(R.layout.fragment_add)
.setNegativeButton(getString(R.string.cancel)) { _, _ ->
// Respond to negative button press
Toast.makeText(context, getString(R.string.cancelled), Toast.LENGTH_SHORT).show()
}
.setPositiveButton(getString(R.string.ok)) { dialogInterface, _ ->
// Respond to positive button press
updateItemInDatabase(dialogInterface)
}
.show()
}
}
Edit:
class RecyclerviewFragment : Fragment() {
private lateinit var mUserViewModel: UserViewModel
private lateinit var adapter: ListAdapter
private var _binding: FragmentRecyclerviewBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
_binding = FragmentRecyclerviewBinding.inflate(inflater, container, false)
mUserViewModel = ViewModelProvider(this)[UserViewModel::class.java]
adapter = ListAdapter{ user -> showUpdateDialog(user)}
val recyclerView = binding.recyclerView
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
// Creates a controller responsible for swiping and moving the views in recyclerview
val itemTouchController = ItemTouchHelper(
object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: ViewHolder, target: ViewHolder,
): Boolean {
// Move specific item from "fromPos" to "toPos" in recyclerview adapter
val fromPos = viewHolder.bindingAdapterPosition
val toPos = target.bindingAdapterPosition
adapter.notifyItemMoved(fromPos, toPos)
return true // true if moved, false otherwise
}
override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
mUserViewModel.deleteUser(adapter.getTaskPosition(viewHolder.bindingAdapterPosition))
Toast.makeText(context, "Task deleted", Toast.LENGTH_SHORT).show()
adapter.notifyItemRemoved(viewHolder.bindingAdapterPosition)
}
})
itemTouchController.attachToRecyclerView(binding.recyclerView)
mUserViewModel.readAllData.observe(viewLifecycleOwner) { user ->
adapter.setData(user)
}
return binding.root
}
private fun updateItemInDatabase(user: User) {
val editText = view?.findViewById<EditText>(R.id.editTextDialog)
val task = editText?.text.toString()
if(inputCheck(task)) {
// Update an entity
mUserViewModel.updateUser(user)
Toast.makeText(context, "Task updated", Toast.LENGTH_SHORT).show()
}
else {
Toast.makeText(context, "Please fill out required fields", Toast.LENGTH_SHORT).show()
}
}
private fun inputCheck(task: String): Boolean {
return !(TextUtils.isEmpty(task))
}
private fun showUpdateDialog(user: User) {
MaterialAlertDialogBuilder(requireContext())
.setView(R.layout.fragment_add)
.setNegativeButton(getString(R.string.cancel)) { _, _ ->
// Respond to negative button press
Toast.makeText(context, getString(R.string.cancelled), Toast.LENGTH_SHORT).show()
}
.setPositiveButton(getString(R.string.ok)) { _, _ ->
// Respond to positive button press
val taskText = view
?.findViewById<EditText>(R.id.editTextDialog)
?.text?.toString()
updateItemInDatabase(user)
}
.show()
}
}
The app doesn't crash when you press ok in the updateDialog anymore, but it doesn't really update the database or the recyclerview items. The cause is that I can't figure out how to update it as I made the list adapter return the whole user(id, task) and don't know how to update only the task. Adding some adapter code to let it explain it by itself.
class ListAdapter(var imageListener:(user: User)->Unit) : RecyclerView.Adapter<ListAdapter.MyViewHolder>() {
...
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = dataSet[position]
holder.taskTitle.text = currentItem.task
holder.editImage.setOnClickListener {
imageListener(getTaskPosition(position))
}
holder.notificationImage.setOnClickListener {
val action = RecyclerviewFragmentDirections.actionRecyclerFragmentToNotificationFragment()
holder.itemView.findNavController().navigate(action)
}
}
fun getTaskPosition(position: Int): User {
return dataSet[position]
}
I get the idea and seem to understand the problem more. Now I see that I didn't use the whole potential of passing the data from adapter, but there is still an issue, if you could guide me through it I would be honored :))
Edit 2:
class RecyclerviewFragment : Fragment() {
private lateinit var mUserViewModel: UserViewModel
private lateinit var adapter: ListAdapter
private var _binding: FragmentRecyclerviewBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
_binding = FragmentRecyclerviewBinding.inflate(inflater, container, false)
mUserViewModel = ViewModelProvider(this)[UserViewModel::class.java]
adapter = ListAdapter{ user -> showUpdateDialog(user)}
val recyclerView = binding.recyclerView
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
// Creates a controller responsible for swiping and moving the views in recyclerview
val itemTouchController = ItemTouchHelper(
object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: ViewHolder, target: ViewHolder,
): Boolean {
// Move specific item from "fromPos" to "toPos" in recyclerview adapter
val fromPos = viewHolder.bindingAdapterPosition
val toPos = target.bindingAdapterPosition
adapter.notifyItemMoved(fromPos, toPos)
return true // true if moved, false otherwise
}
override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
mUserViewModel.deleteUser(adapter.getTaskPosition(viewHolder.bindingAdapterPosition))
Toast.makeText(context, "Task deleted", Toast.LENGTH_SHORT).show()
adapter.notifyItemRemoved(viewHolder.bindingAdapterPosition)
}
})
itemTouchController.attachToRecyclerView(binding.recyclerView)
mUserViewModel.readAllData.observe(viewLifecycleOwner) { user ->
adapter.setData(user)
}
return binding.root
}
#SuppressLint("NotifyDataSetChanged")
private fun updateItemInDatabase(user: User) {
val editText = view?.findViewById<EditText>(R.id.editTextDialog)
val task = editText?.text.toString()
if(inputCheck(task)) {
// Update an entity
mUserViewModel.updateUser(user)
Toast.makeText(context, "Task updated", Toast.LENGTH_SHORT).show()
adapter.notifyDataSetChanged()
}
else {
Toast.makeText(context, "Please fill out required fields", Toast.LENGTH_SHORT).show()
}
}
private fun inputCheck(task: String): Boolean {
return !(TextUtils.isEmpty(task))
}
private fun showUpdateDialog(user: User) {
MaterialAlertDialogBuilder(requireContext())
.setView(R.layout.fragment_add)
.setNegativeButton(getString(R.string.cancel)) { _, _ ->
// Respond to negative button press
Toast.makeText(context, getString(R.string.cancelled), Toast.LENGTH_SHORT).show()
}
.setPositiveButton(getString(R.string.ok)) { _, _ ->
// Respond to positive button press
val taskText = view
?.findViewById<EditText>(R.id.editTextDialog)
?.text?.toString()
updateItemInDatabase(User(user.id, taskText.toString()))
}
.show()
}
}
With this code it seems like the function is working, however, it can't access the taskText value? If I try to edit any of the tasks in the emulator it updates to "null" Providing the ViewModel, but I don't think there is an issue there. It is probably rooted somewhere in the value itself.
UserViewModel.kt:
class UserViewModel(application: Application) : AndroidViewModel(application) {
val readAllData: LiveData<List<User>>
private val repository: UserRepository
init {
val userDao = UserDatabase.getDatabase(application).userDao()
repository = UserRepository(userDao)
readAllData = repository.readAllData
}
fun addUser(user: User) {
viewModelScope.launch(Dispatchers.IO) {
repository.addUser(user)
}
}
fun updateUser(user: User) {
viewModelScope.launch(Dispatchers.IO) {
repository.updateUser(user)
}
}
fun deleteUser(user: User) {
viewModelScope.launch(Dispatchers.IO) {
repository.deleteUser(user)
}
}
}
Just as a quick sketch so you see what I'm talking about:
// your adapter already takes a callback function - make it send some data
// about the item being clicked. I'm assuming it's an ID here but you could
// pass back a specific object too
class ListAdapter(
private val onDeleteListener: (itemId: Int) -> Unit
) ...
In the Fragment
// onCreate
adapter = ListAdapter { itemId -> showUpdateDialog(itemId) }
// dialog function should take the ID as a parameter
private fun showUpdateDialog(itemId: Int) {
...
.setPositiveButton(getString(R.string.ok)) { dialogInterface, _ ->
// Don't send the dialog interface - pass the actual data you want to use
val taskText = (dialogInterface as AlertDialog)
.findViewById<EditText>(R.id.editTextDialog)
?.text?.toString()
updateItemInDatabase(itemId, taskText)
}
...
}
// Your update function acts on specific data - it has no knowledge of how the rest
// of the app is implemented, it's not hardwired into other components etc
private fun updateItemInDatabase(itemId: Int, task: String?) {
if(inputCheck(task)) {
mUserViewModel.updateUser(itemId)
}
}
See how much simpler that is? You have a clear direction of data flow, where the Adapter hands a specific piece of data to its callback function, which passes it to the confirmation dialog, which passes it to the update function which needs that specific piece of data. The only involvement the Adapter has is saying "hey, an item was clicked, here's the info". You don't need to go asking for more details later, like "so hey what are you currently displaying" - that was passed as part of the event's data.
That's cleaner in general, but especially with RecyclerViews you don't want to be poking at their internals, keeping references to ViewHolders etc, because that state is volatile. The way they work is by reusing those objects to display different data, so keeping long-running references to them assuming they're displaying a particular item is asking for trouble. It probably doesn't matter so much here (if you tap an item to get a dialog the user probably can't get it to scroll to another position) but it's better to not do that thing at all.
also btw, this is a bug:
// top-level variable
adapter = ListAdapter{showUpdateDialog()}
// local variable
val adapter = ListAdapter{showUpdateDialog()}
// local variable
recyclerView.adapter = adapter
You're creating two separate instances of your ListAdapter - one is stored long-term, the other is the one you actually set on your RecyclerView. The long-term one is what you're accessing in updateItemInDatabase, the one that's not actually being used by a RecyclerView, so it's not the thing that was actually being clicked (and it won't have any ViewHolders yet either). This is why it's better to just pass data in one direction if you can, less chance of complications being introduced!
I'm really confused about how the Kotlin lambdas work, specifically with click listeners. I had something that was working to do a single ViewModel function in my MainFragment but now I want multiple buttons on my adapter that do different things. At first I thought I would just have to pass all the necessary information including IDs for the different buttons to the callback then do a switch statement in my main fragment that does the appropriate ViewModel functions. As soon as I changed my input parameters the adapter no longer accepted my OnClickListener argument.
First I'll show the old OnClickListener that was working.
ItemAdapter
class ItemAdapter(private val context: Context, private val onClickListener: OnClickListener) : ListAdapter<SongWithRatings, ItemAdapter.SongViewHolder>(SongsComparator())
{
lateinit var isVisible: BooleanArray
override fun onCurrentListChanged(
previousList: List<SongWithRatings>,
currentList: List<SongWithRatings>
) {
super.onCurrentListChanged(previousList, currentList)
if(previousList.size != currentList.size) {
isVisible = BooleanArray(itemCount)
isVisible.fill(element = false)
}
}
class SongViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
val textViewBpm: TextView = view.findViewById(R.id.bpm)
val lastPlayed: TextView = view.findViewById(R.id.lastPlayed)
//new rating code 11/27/2021
val ratingBar: SeekBar = view.findViewById(R.id.ratingBar)
val ratingLabel: TextView = view.findViewById(R.id.ratingLabel)
val button: Button = view.findViewById(R.id.submitRating)
//expandable view 3/27/2022
val titleView: LinearLayout = view.findViewById(R.id.titleView)
val expand: ConstraintLayout = view.findViewById(R.id.expand)
//fragment launch buttons
val moreButton: Button = view.findViewById(R.id.moreButton)
val rateButton: Button = view.findViewById(R.id.rateButton)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return SongViewHolder(adapterLayout)
}
override fun onBindViewHolder(holder: SongViewHolder, position: Int) {
val item = getItem(position)
//temporary code for initial rating
val initialRating = item.recentPerformanceRating()
var newRating = 0
holder.textView.text = item.song.songTitle
holder.textViewBpm.text = context.resources.getString(R.string.BPM,item.song.bpm)
holder.ratingLabel.setBackgroundColor(getStatusColor(item.recentPerformanceRating()))
//holder.imageView.setImageResource(item.imageResourceID)
holder.ratingLabel.text = context.resources.getString(R.string.Rating, initialRating )
holder.lastPlayed.text = item.lastPlayedString()
//rating bar functionality
holder.ratingBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, rating: Int, fromUser: Boolean) {
holder.ratingLabel.text = context.resources.getString(R.string.Rating, rating)
newRating = rating
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
//Performance Rating button functionality
holder.button.setOnClickListener{
onClickListener.onClick(item.song.songTitle, newRating)
}
holder.moreButton.setOnClickListener { }
class OnClickListener(val clickListener: (songTitle: String, newRating: Int) -> Unit) {
fun onClick(songTitle: String,newRating: Int) = clickListener(songTitle, newRating)
}
}
From MainFragment
val recyclerView = view.findViewById<RecyclerView>(R.id.recycler_view)
val adapter = ItemAdapter(requireContext(),
ItemAdapter.OnClickListener { songTitle, newRating ->
songViewModel.insertRating( Rating(System.currentTimeMillis(),songTitle,songViewModel.artistName, newRating )) }
)
Like I said, this all worked fine until I tried to use the OnClickListener with a SongsWithRatings parameter.
Am I even close here or do I have to redo my whole interface between the adapter, fragment and ViewModel?
You just have to make use of Interface for the purpose of providing listeners to the Fragment .
Step 1: Create an Interface Class.
interface ItemClickListener{
//You can include the parameters into the functions which you wish to be associated with the button. Suppose I want Title on Click of more Button, then I will pass it as a parameter
fun onButtonClick(val item : SongsWithRating)
fun onMoreButtonClicked(val title : String)
}
Step 2 : Create a listener variable in your adapter and call the functions in the onClick function of the respective buttons
class ItemAdapter(private val context: Context) : ListAdapter<SongWithRatings, ItemAdapter.SongViewHolder>(SongsComparator())
{
var listener : ItemClickListener ?= null
override fun onBindViewHolder(holder: SongViewHolder, position: Int) {
val item = getItem(position)
//Calling buttonClick and passing the function defined in the interface
along with the parameters
holder.button.setOnClickListener{
listener?.onButtonClick(item)
}
//Similarly for morebutton
holder.moreButton.SetOnClickListener{
listener?.onMoreButtonClicked(item.song.songTitle)
}
}
}
Step 3 : Now the final Step : Go to the fragment wherein the recyclerView is implemented
//Extent the Fragment with the Interface and override the methods
class Fragment : Fragment(), ItemClickListener{
//define Adapter and attach the listener
val adapter = ItemAdapter(requireContext()
adapter.listener = this
}
You are good to go
I have this fragment in which I store my 'favorite items' and I can delete them when I click on a button if I want to. The implementation works well until I get to the last item and it doesn't disappear unless I go to another fragment and then come back (as in, the item is deleted but the recycler view still shows it unless I update the fragment myself).
How can I make the last item disappear right away? Setting notifyDataSetChanged() after the deleteHandler in the adapter does not seem to work.
This is the fragment where I have the items:
class FavoritesFragment : Fragment() {
private val mfavoriteViewModel by viewModels<FavoriteViewModel>()
private lateinit var binding: FragmentFavoritesBinding
private val deleteHandler: (Favorites) -> Unit = {
mfavoriteViewModel.deleteFavorite(it)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
binding = FragmentFavoritesBinding.inflate(layoutInflater)
//recyclerview
val adapter = FavoritesAdapter(deleteHandler)
binding.rvFavList.layoutManager = LinearLayoutManager(context)
binding.rvFavList.adapter = adapter
//favoriteViewModel
mfavoriteViewModel.readAllData.observe(viewLifecycleOwner, { favorite ->
if (favorite.isEmpty()) {
binding.emptyState.text = getString(R.string.emptyState)
binding.emptyState.visibility = View.VISIBLE
} else {
adapter.setData(favorite)
binding.emptyState.visibility = View.GONE
}
})
return binding.root
}
}
The adapter:
class FavoritesAdapter(val deleteHandler: (Favorites) -> Unit) :
RecyclerView.Adapter<FavoritesAdapter.ViewHolder>() {
private var favoriteList = emptyList<Favorites>()
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val binding = FavItemBinding.bind(itemView)
val favTitle: TextView = binding.tvFavsTitle
val favItem: ImageButton = binding.btnFavs
val favImg: ImageView = binding.ivFavs
fun bind(favorites: Favorites) {
Picasso.get().load(favorites.image).into(favImg)
favTitle.text = favorites.title
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.fav_item, parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(favoriteList[position])
//delete favorite item
holder.favItem.setOnClickListener {
deleteHandler(favoriteList[position])
}
}
override fun getItemCount(): Int {
return favoriteList.size
}
fun setData(favorite: List<Favorites>) {
this.favoriteList = favorite
notifyDataSetChanged()
}
}
This is the favorite's viewmodel:
class FavoriteViewModel(application: Application) : AndroidViewModel(application) {
val readAllData: LiveData<List<Favorites>>
private val repository: FavoritesRepository
init {
val favoriteDao = FavoriteDatabase.getDatabase(application).favoriteDao()
repository = FavoritesRepository(favoriteDao)
readAllData = repository.readAllData
}
fun addFavorite(favorite: Favorites) {
viewModelScope.launch(Dispatchers.IO) {
repository.addFavorite(favorite)
}
}
fun deleteFavorite(favorite: Favorites) {
viewModelScope.launch(Dispatchers.IO) {
repository.deleteFavorite(favorite)
}
}
fun deleteAllFavorites() {
viewModelScope.launch(Dispatchers.IO) {
repository.deleteAllFavorites()
}
}
}
Here in your observer:
mfavoriteViewModel.readAllData.observe(viewLifecycleOwner, { favorite ->
if (favorite.isEmpty()) {
binding.emptyState.text = getString(R.string.emptyState)
binding.emptyState.visibility = View.VISIBLE
} else {
adapter.setData(favorite)
binding.emptyState.visibility = View.GONE
}
})
When the list goes from one item to zero items, in the if block you show an empty message, but you fail to update the adapter data or hide the RecyclerView so it will continue to show what it did before. You should move the adapter.setData(favorite) outside the if/else.
Clear your favourites list before setting the new items in it. You can do this in your setData() function. Like this,
fun setData(favorite: List<Favorites>) {
if (favouriteList.isNotEmpty()) {
favouriteList.clear()
}
this.favoriteList = favorite
notifyDataSetChanged()
}
I have an arraylist (called Itemlist) of all recyclerview elements. In each element there are 2 textviews - a german and english word. only one of them is shown (because they overlap). when i click on the element it shows the other language (for example: the german word is set to gone and the english word is visible now).
Now I want a function which sets all english textviews (in every element) to gone and the german to visible. My problem is - i dont know how to reach all elements in this arraylist and check the visibility of the textviews. in my example it resets only the first word.
For better understanding
Here is the code:
fun reset_to_EN() {
ItemList.forEach { test_if_german() }
}
OR
fun reset_to_EN2() {
for (item in ItemList) {
test_if_german()
}
}
Check visibility
fun test_if_german(){
if (text_view_de.visibility == View.VISIBLE) {
text_view_en.visibility = View.VISIBLE
text_view_de.visibility = View.GONE
}
adapter.notifyDataSetChanged()
}
If you can please show me a code example for better understanding.
Thanks to everyone who tries to help.
Or here is the whole code for the adapter and mainActivity if it's needed:
class Adapter(
val c: Context,
private val ArrList: ArrayList<Item>):
RecyclerView.Adapter<Adapter.ViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
return ViewHolder(inflater)
}
override fun getItemCount() = ArrList.size
inner class ViewHolder(v: View) : RecyclerView.ViewHolder(v), View.OnClickListener {
var textViewDe: TextView = v.text_view_de
var textViewEn: TextView = v.text_view_en
private var menueImage: Button
init {
v.setOnClickListener(this)
textViewDe = v.findViewById(R.id.text_view_de)
textViewEn = v.findViewById(R.id.text_view_en)
menueImage = v.findViewById(R.id.menu_button)
menueImage.setOnClickListener { popupMenu(it) }
}
private fun popupMenu(v:View) {
val drop = PopupMenu(c, v)
val position = ArrList[adapterPosition]
drop.inflate(R.menu.drop_menu)
drop.setOnMenuItemClickListener {
when(it.itemId){
R.id.edit_menu->{
val v2 = LayoutInflater.from(c).inflate(R.layout.add_item_layout,null)
val DE = v2.findViewById<EditText>(R.id.editText)
val EN = v2.findViewById<EditText>(R.id.editText2)
AlertDialog.Builder(c)
.setView(v2)
.setPositiveButton("Ok"){
dialog,_->
position.Englisch = DE.text.toString()
position.Deutsch = EN.text.toString()
notifyDataSetChanged()
//Toast.makeText(c,"User Information is Edited",Toast.LENGTH_SHORT).show()
dialog.dismiss()
}
.setNegativeButton("Cancel"){
dialog,_->
dialog.dismiss()
}
.create()
.show()
true
}
R.id.delete_menu-> {
ArrList.removeAt(adapterPosition)
notifyDataSetChanged()
//Toast.makeText(c,"entfernt",Toast.LENGTH_SHORT).show()
true
}
else -> true
}
}
drop.show()
val popup = PopupMenu::class.java.getDeclaredField("mPopup")
popup.isAccessible = true
val menu = popup.get(drop)
menu.javaClass.getDeclaredMethod("setForceShowIcon",Boolean::class.java)
.invoke(menu,true)
}
override fun onClick(p0: View?) {
if (textViewDe.visibility == View.VISIBLE) {
textViewDe.visibility = View.GONE
textViewEn.visibility = View.VISIBLE
} else {
textViewDe.visibility = View.VISIBLE
textViewEn.visibility = View.GONE
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = ArrList[position]
holder.textViewDe.text = currentItem.Deutsch
holder.textViewEn.text = currentItem.Englisch
}
And MainActivity:
class MainActivity : AppCompatActivity() {
//DEFINITION
private lateinit var addButton: FloatingActionButton
private lateinit var ItemList: ArrayList<Item>
private lateinit var recy: RecyclerView
private lateinit var adapter: Adapter
//ONCREATE
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//FINDVIEWBYID
addButton = findViewById(R.id.addingBtn)
ItemList = ArrayList()
recy = findViewById(R.id.recycler_view)
//RECYCLERVIEW
adapter = Adapter(this, ItemList)
recy.layoutManager = LinearLayoutManager(this)
recy.adapter = adapter
//FUNCTION-CALL
addButton.setOnClickListener { addInfo() }
}
//FUNKTIONENS
private fun addInfo() {
val inflter = LayoutInflater.from(this)
val v = inflter.inflate(R.layout.add_item_layout, null) //
val eng = v.findViewById<EditText>(R.id.editText)
val deu = v.findViewById<EditText>(R.id.editText2)
val addDialog = AlertDialog.Builder(this)
addDialog.setView(v)
addDialog.setPositiveButton("OK"){ dialog, _->
val eng2 = eng.text.toString()
val deu2 = deu.text.toString()
val UUID = UUID.randomUUID()
ItemList.add(Item(UUID, eng2, deu2))
adapter.notifyDataSetChanged()
//Toast.makeText(this, "Adding Success", Toast.LENGTH_SHORT).show()
dialog.dismiss()
}
addDialog.setNegativeButton("Cancel"){ dialog, _->
dialog.dismiss()
}
addDialog.create()
addDialog.show()
}
fun clearData() {
ItemList.clear()
adapter.notifyDataSetChanged()
Toast.makeText(this, "Alles gelöscht", Toast.LENGTH_SHORT).show()
}
fun reset_all_EN() {
//ArrayList = ItemList
val size: Int = ItemList.size
for (i in 0 until size) {
if (text_view_de.visibility == View.VISIBLE) {
text_view_en.visibility = View.VISIBLE
text_view_de.visibility = View.GONE
}
adapter.notifyDataSetChanged()
}
}
fun reset_to_EN() {
// using forEach() method
ItemList.forEach { test_if_german() }
}
fun reset_to_EN2() {
for (item in ItemList) {
test_if_german()
}
}
fun test_if_german(){
if (text_view_de.visibility == View.VISIBLE) {
text_view_en.visibility = View.VISIBLE
text_view_de.visibility = View.GONE
}
adapter.notifyDataSetChanged()
}
//MENU CLASSES
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.open_menu -> {
val intent = Intent(this, InfoActivity::class.java)
startActivity(intent)
}
R.id.open_menu2 -> {
val intent = Intent(this, SettingsActivity::class.java)
startActivity(intent)
}
R.id.reset_all -> {
reset_to_EN2()
}
}
return super.onOptionsItemSelected(item)
}
}
Since I don't see any declaration of text_view_de or text_view_en, I'm guessing you're using synthetic view properties from the deprecated Android Kotlin Extensions. Assuming that is the case:
When you use text_view_de, it is performing a search in your view hierarchy for the first view it finds with the matching ID. So even though you are doing it within a for loop that iterates through your list of items, you are only working with the same view, over and over.
Edit:
I realized you want to be able to toggle individual views and you were only asking how to add a button to reset all views back to the same language. If this is the case, it does not make sense to add a property to the adapter that controls the state of all views at once like I had suggested in the previous revision of this answer.
Instead, you need to change your data model to have a Boolean that determines which specific language that specific item should show. The problem with how you're doing it now in your click listener is that it is trying to use the Views themselves to determine what state the item is when you change it, but this will cause weird glitches when items scroll off of the screen and back on because ViewHolders get recycled and assigned to different items when they go off and back on screen.
To get started, add a Boolean for the state of the item to your Item class. I don't know exactly what your class looks like now, so adapt this as needed:
data class Item (
val UUID: Long,
val english: String,
val deutsch: String,
var isShowDeutch: Boolean = true
)
A good practice is to have your Adapter class expose a callback for items being clicked so the outside class (Activity) is responsible for manipulating the data model and the Adapter's responsibility is limited to connecting data to views, not manipulating data. So create a callback that the Activity can implement that toggles a single Item's isShowDeutsch property. And when you bind data to a view, use that item's isShowDeutsch to determine visibility.
In Adapter class:
var onItemClickListener: ((itemPosition: Int)->Unit)? = null
//...
// In ViewHolder:
override fun onClick(view: View) {
itemClickListener?.invoke(adapterPosition)
}
//...
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = ArrList[position]
holder.textViewDe.text = currentItem.Deutsch
holder.textViewEn.text = currentItem.Englisch
holder.textViewDe.isVisible = currentItem.isShowDeutsch
holder.textViewEn.isVibible = !currentItem.isShowDeutsch
}
In your Activity when you set up your adapter, you can define a click listener for it that toggles the state of that single item and notifies the adapter of the change:
//RECYCLERVIEW
adapter = Adapter(this, ItemList)
recy.layoutManager = LinearLayoutManager(this)
recy.adapter = adapter
adapter.onItemClickListener = { position ->
ItemList[position].apply { isShowDeutsch = !isShowDeutsch }
adapter.notifyItemChanged(position)
}
And finally, to reset all items back to their original language, you can iterate the items in your list and then notify the adapter. This is more appropriate to do in your Activity, since the Adapter should not be responsible for manipulating data.
fun resetLanguage() {
for (item in ItemList) {
item.isShowDeutsch = true
}
adapter.notifyDataSetChanged()
}
I also recommend you change lateinit var ItemList: ArrayList<Item> to val ItemList = ArrayList<Item>(). It is error prone to have a mutable list type in a mutable var property because there are two different ways to change it and it creates the possibility of having your adapter looking at a different list than the one your Activity is working with.
total beginner here. Seems like I am missing something very basic here, so any resources to what it is or a good explanation would be greatly appreciated.
I added a Recycler View with PagerSnapHelper. I swipe left and right, and everything works fine. Now, in a TextView in my activity_main.xml I would like the app to show Adapter's current position.
What would be the best way to do this?
I tried couple of things eg. val pozycja adaptera = recyclerView.layoutManager.findContainingItemView() or some weird instances of getAdapterPosition()
but I think they are either not suited for the occasion or I am simply using them wrong.
Have a look at code below:
class MainActivity : AppCompatActivity() {
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Adding RecyclerView
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview_id)
recyclerView.run {
layoutManager = LinearLayoutManager(
this#MainActivity,
LinearLayoutManager.HORIZONTAL,
false)
adapter = MyAdapter()
// This makes mood_layout snap to grid (full screen)
PagerSnapHelper().attachToRecyclerView(this)
}
//Accessing Recycler View position
val widokPozycji = findViewById<TextView>(R.id.textView_position)
val pozycjaAdaptera = recyclerView.layoutManager.findContainingItemView()
//Adding AlertDialog to add comments
btn_addNote.setOnClickListener {
//Setting Inflater to inflate Dialog with comment_edit_text.xml layout
val dialogLayout = LayoutInflater.from(this).inflate(R.layout.comment_edit_text, null)
val builder = AlertDialog.Builder(this)
.setView(dialogLayout)
.show()
//Adding Shared Preferences to save a comment on confirmCommentButton button
builder.confirmCommentButton.setOnClickListener {
//creating instance of Shared Preferences
val pref = getSharedPreferences("commentSharedPreferences", Context.MODE_PRIVATE)
val editor = pref.edit()
//ACCESSING COMMENT WRITTEN BY USER in AlertDialog.Builder - val builder
val insertedName = builder.editTextComment.text.toString()
//Saving shared Preferences
editor.apply {
putString("STRING_KEY", insertedName)
apply()
}
// Toast to confirm saved data
Toast.makeText(this, "Comment Saved", Toast.LENGTH_SHORT).show()
//close Dialog on CONFIRM button click
builder.dismiss()
}
//CANCEL button that closes Dialog on click
builder.cancelCommentButton.setOnClickListener{
builder.dismiss()
}
builder.setOnDismissListener {
}
}
}
//HISTORY button taking user to HistoryActivity on click
fun history(view: View) {
// New Activity to open HistoryActivity
var historyActivity: Intent = Intent(applicationContext, HistoryActivity::class.java)
startActivity(historyActivity)
}
}
Adapter:
class MyViewHolder(val view : View):RecyclerView.ViewHolder(view)
class MyAdapter : RecyclerView.Adapter<MyViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val moodInflater = layoutInflater.inflate(R.layout.mood_layout, parent, false)
return MyViewHolder(moodInflater)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val context = holder.view.context
val emoji = holder.view.emoji_img
val background = holder.view.moodLayout_id
val moodSelected = position
when(moodSelected){
0 -> {emoji.setImageResource(R.drawable.smiley_super_happy)
background.setBackgroundColor(context.resources.getColor(R.color.banana_yellow))
}
1 -> {emoji.setImageResource(R.drawable.smiley_happy)
background.setBackgroundColor(context.resources.getColor(R.color.light_sage))
}
2 -> {emoji.setImageResource(R.drawable.smiley_normal)
background.setBackgroundColor(context.resources.getColor(R.color.cornflower_blue_65))
}
3 -> {emoji.setImageResource(R.drawable.smiley_disappointed)
background.setBackgroundColor(context.resources.getColor(R.color.warm_grey))
}
4 -> {emoji.setImageResource(R.drawable.smiley_sad)
background.setBackgroundColor(context.resources.getColor(R.color.faded_red))
}
}
}
override fun getItemCount(): Int {
return 5
}
}
Ok, so I used Shared Preferences to store position and to be able to access it from other activities. I put them in onBindViewHolder. Code looks like this:
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val context = holder.view.context
val sharedPref = holder.view.context.getSharedPreferences("position", MODE_PRIVATE)
val editor = sharedPref.edit()
editor.putInt("POSITION_KEY", position)
editor.apply()