How to access RecyclerView position - android

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

Related

Initialize viewHolder in Fragment

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!

Adding a single choice alert dialog into a recycler view

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.

How can i change the visibility of a textview in every element of the recyclerview?

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.

How to disable the auto scroll of a RecyclerView (ListAdapter) that happens when an item is updated?

BACKGROUND
I have a UI that shows a list of users' fullnames with a like/dislike button for each item. I am using a ListAdapter that under the hood uses DiffUtil and AsyncListDiffer APIs. The list of users is received as a LiveData from a Room database and it's ordered by "isLiked".
PROBLEM
Whenever the like button is tapped, Room as I am using a LiveData will re-submit the new data to the adapter. The problem is that as the list is ordered by "isLiked", the liked user will change its position and the RecyclerView will always sroll to the new position.
I don't want to see the new position of the updated item. So, how can I disable the auto scroll behavior?
WHAT I TRIED
MainActivity.kt
..
val userAdapter = UsersAdapter(this)
val ll = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recyclerView.apply {
layoutManager = ll
adapter = userAdapter
itemAnimator = null
setHasFixedSize(true)
}
viewModel.users.observe(this, {
// This will force the recycler view to scroll back to the previous position
// But it's more of a workaround than a clean solution.
val pos = ll.findFirstVisibleItemPosition()
userAdapter.submitList(it) {
recyclerView.scrollToPosition(pos)
}
})
..
UsersAdapter.kt
class UsersAdapter(
private val clickListener: UserClickListener
) : ListAdapter<UserEntity, UsersAdapter.UserViewHolder>(DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false)
return UserViewHolder(view)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val userEntity = getItem(position)
holder.bind(userEntity, clickListener)
}
class UserViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val textView: TextView = view.findViewById(R.id.fullName)
private val fav: ImageButton = view.findViewById(R.id.fav)
fun bind(user: UserEntity, clickListener: UserClickListener) {
textView.text = user.fullName
val favResId = if (user.favorite) R.drawable.like else R.drawable.dislike
fav.setImageResource(favResId)
fav.setOnClickListener {
val newFav = !user.favorite
val newFavResId = if (newFav) R.drawable.like else R.drawable.dislike
fav.setImageResource(newFavResId)
clickListener.onUserClicked(user, newFav)
}
}
}
interface UserClickListener {
fun onUserClicked(user: UserEntity, isFavorite: Boolean)
}
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<UserEntity>() {
override fun areItemsTheSame(
oldUser: UserEntity,
newUser: UserEntity
) = oldUser.id == newUser.id
override fun areContentsTheSame(
oldUser: UserEntity,
newUser: UserEntity
) = oldUser.fullName == newUser.fullName && oldUser.favorite == newUser.favorite
}
}
}
I tried using a regular RecyclerView adapter and DiffUtil with detect moves set to false.
I added the AsyncListDiffer as well.
I tried the ListAdapter, and even tried the paging library and used the PagedListAdapter.
DiffUtil's callback changes the auto scrolling, but i couldn't get the desired behavior.
Any help is greatly appreciated!

setOnLongClickListener in kotlin android

How can I use setOnClickListener in each item in my ListView?
my xml :
<ListView
android:id="#+id/tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
in your kotlin activity:
override fun onCreate(savedInstanceState: Bundle?) {
val listView: ListView = findViewById(R.id.yourListViewId)
listView.onItemClickListener = AdapterView.OnItemClickListener { adapterView, view, i, l ->
//YOUR CODE HERE
}
}
Nothing like getting to the party late. We are posting this answer because we struggled with setting a OnLongClickListener in our RecyclerAdapter Why because as you enter the code if the RETURN value is not included before adding the lines of code between the opening statement and the return the compiler complains and one would think they are just wrong here is a little code hope it helps anyone new to OnLongClickListener
class PersonRecyclerAdapter(contactList: List<Contact>, internal var context: Context) : RecyclerView.Adapter<PersonRecyclerAdapter.ViewHolder>() {
private var contactList: List<Contact> = ArrayList()
init { this.contactList = contactList }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.new_single_card,parent,false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return contactList.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val items = contactList[position]
holder.item.text = items.name
holder.bindCheckBox()
// This code calls the function below in the inner class
// too much code manipulation
holder.list_new_card.setOnLongClickListener { view ->
holder.ckBox.isChecked = false
holder.ckBox.isEnabled = true
holder.item.text = items.name
true
}
/*holder.list_new_card.setOnClickListener {
holder.ckBox.isChecked = false
holder.ckBox.isEnabled = true
holder.item.text = items.name
//val i = Intent(context, MainActivity::class.java)
//i.putExtra("Mode", "E")
//i.putExtra("Id", items.id)
//i.putExtra("ET",items.name)
//i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
//context.startActivity(i)
// This code attaches a listener to the tvName in the new_single_card.xml
}*/
holder.editCLICK.setOnClickListener {
val i = Intent(context, MainActivity::class.java)
i.putExtra("FROM", "U")
i.putExtra("MainActId",items.id)
i.putExtra("ET",items.name)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(i)
}
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var item: TextView = view.findViewById(R.id.tvName) as TextView
var list_new_card: CardView = view.findViewById(R.id.list_new_card) as CardView
var editCLICK: RelativeLayout = view.findViewById(R.id.editCLICK) as RelativeLayout
var ckBox:CheckBox = view.findViewById(R.id.ckBox)as CheckBox
// This is how you declare a instance of the Widiget you want to work with
fun bindCheckBox(){// Create function and BIND it in the onBindViewHolder function
ckBox.setOnCheckedChangeListener { view,isChecked ->
if(ckBox.isChecked){
item.visibility = View.VISIBLE
item.setTextColor(Color.parseColor("#FF0000"))
item.text = "Click & HOLD Me to View Item"
ckBox.isEnabled = false
}else
item.setTextColor(Color.parseColor("#000000"))
}
}
}
}
Note different ways to include listeners in RecyclerAdapter

Categories

Resources