I have had a look at various stackoverflow questions and answers to this problem and still struggling to get the right solution. If someone has a link to my problem with a solution I will mark it as duplicate but I have not found one yet.
Note: Code has been upgraded to Kotlin so mindful of some code may not be optimized for Kotlin best practices yet.
I have captured the button pressed in my RecyclerAdapter and now want it passed back to my Activity that has the recyclerView.addOnItemTouchListener.
My RecyclerAdapter code is as follows:
override fun onBindViewHolder(holder: NotesPicsRecyclerAdapter.RecyclerViewHolder, position: Int) {
val data_provider = arrayList[position]
holder.notePicShareButton.setOnClickListener {
Toast.makeText(context, "Share Button", Toast.LENGTH_SHORT).show()
}
holder.notePicChangePicButton.setOnClickListener {
Toast.makeText(context, "Edit Button", Toast.LENGTH_SHORT).show()
}
holder.notePicDeleteButton.setOnClickListener {
Toast.makeText(context, "Delete Button", Toast.LENGTH_SHORT).show()
}
try {
holder.imageText.text = data_provider.notes_text
val imageFile = data_provider.image_path
val inputStream = context.assets.open(imageFile)
val d = Drawable.createFromStream(inputStream, null)
holder.imagePath.setImageDrawable(d)
} catch (e: IOException) {
e.printStackTrace()
}
}
class RecyclerViewHolder(view: View) : RecyclerView.ViewHolder(view) {
internal var imagePath: ImageView
internal var imageText: TextView
internal var notePicShareButton: Button
internal var notePicChangePicButton: Button
internal var notePicDeleteButton: Button
init {
imagePath = view.findViewById<View>(R.id.single_note_pic_image) as ImageView
imageText = view.findViewById<View>(R.id.single_note_pic_text) as TextView
notePicShareButton = view.findViewById<View>(R.id.single_note_pic_share_button) as Button
notePicChangePicButton = view.findViewById<View>(R.id.single_note_pic_change_pic_button) as Button
notePicDeleteButton = view.findViewById<View>(R.id.single_note_pic_delete_button) as Button
}
}
My onCreate in my Activity code snippet is as follows:
recyclerView.addOnItemTouchListener(
MyRecyclerViewClickListener(this, object : MyRecyclerViewClickListener.OnItemClickListener {
override fun onItemClick(view: View, position: Int) {
val thePos = arrayList[position].list_id
Toast.makeText(applicationContext, "This row: $thePos", Toast.LENGTH_SHORT).show()
}
})
)
All Toast messages in my RecyclerAdapter and Activity work so I have captured which button is being pressed as well as the row being selected, but I need the button information to be passed back to my Activity so I can use it with the row that was pressed. Not sure how to proceed and still digging but any info would be appreciated.
I think you can do this.
Create Variable and setter of that variable in your activity.
private YourObject localVariable;
public YourObject setLocalVariable(YourObject localVariable){
this.localVariable= localVariable;
}
And on Clicking on recycle View List Listener.
Typecast your context into your activity and set your Local variable over there.
((YourActivity)context).setLocalVariable(arrayList[position])
You can use the EventBus library it will achieve what you want easy
https://github.com/greenrobot/EventBus
Related
I need help with composables and mutableStates!
In the example below, I have a list of classes.
I want to try to manipulate the list to
a. swap the position of two items
b. change the "data" in one of the classes
c. get a copy of the classes "flattened"
I have in the main composable a text field displaying the items
a. a copy of the list from the viewModel
b. a button to move the 1st item to the 3rd position
c. a button to get the "flattened" data
d. a button to modify the data in the 3rd item.
I expected each of these actions to modify the visible contents
of the items.
When I run this I see the items on the screen.
When I click "move 2nd to 4th", the list of items does not update on the screen.
But, clicking to get the flattened layout shows the flattened data correctly.
Now, I click "change data" and the data is changed in the items on the screen!
If I click on get flat layout, the original list reappears and the
flattened list shows the original rlattened list again!
What is happening and how can I fix it? I don't expect coroutine calls are
needed here.
I also have the problem of losing data when I rotate the screen (newbie here.)
Pertinent code of MainActivity:
#Composable
fun Screen() {
val vm = MyViewModel()
val items = vm.items.collectAsState()
var flat by remember{ mutableStateOf ("")}
var newItem by remember { mutableStateOf(MyClass("",""))}
Column {
Text(text = items.toString().split("MyC").joinToString("\nMyC"))
Spacer(Modifier.height(8.dp))
Button(
onClick = {vm.moveList(1,3)} // swap 2nd and 4th items on list
) { Text( text = "move 2nd to 4th")}
Spacer(Modifier.height(8.dp))
Button(
onClick = {flat = vm.getLayout()}
) { Text( text = "get flat layout")}
Spacer(Modifier.height(8.dp))
Text(text = flat)
Spacer(Modifier.height(8.dp))
Button(
onClick = { vm.changeData(2,"NEW")
newItem = vm.items.value[2]}
) { Text( text = "get change heading on 3rd")}
Spacer(Modifier.height(8.dp))
Text(text = newItem.toString())
}
}
And here is code from the viewModel:
data class MyClass(
val key: String,
val data: String,
)
class MyViewModel: ViewModel(){
companion object {
private const val DEFAULT_LAYOUT = "0|A,1|B,2|C,3|D,4|E"
}
private val _items: MutableStateFlow<List<MyClass>> =
MutableStateFlow(
DEFAULT_LAYOUT.split(",").map{ MyClass(it.split("|").first(),it.split("|").last())})
val items: StateFlow<List<MyClass>> = _items
fun getLayout(): String {
return items.value.map { it.key + "|" + it.data }.joinToString(",")
}
fun changeData(
index: Int,
newData: String
) {
val mC : MyClass = items.value[index].copy(data = newData)
_items.update {
it.toMutableList().apply {
set(index,mC)
}
}
}
fun moveList(from: Int, to: Int) {
_items.update {
it.toMutableList().apply {
add(to, removeAt(from))
}
}
}
fun removeElement(idx: Int) {
_items.update {
it.toMutableList().apply {
removeAt(idx)
}
}
}
}
Good day. So I currently have data in my recycler view. It is for now only static data. I still have to do the code where I import. My problem however is I have a button that changes the background of a text view. This happens in my adapter. And when I scroll through my list the bg color change gets reverted back to what it was before the button click. I have read a lot of similar problems but could not really find one that explains clearly or work for me. From what I read the data gets reset to the static data because it is currently happening in my onBindViewHolder and I think this changes the data on every new data read(scrolling). I read that I should create a link or a listener and then call it. But It does not make sense to me because if a link is called the same amount of times as the code is executed then it will be the same will it not. Maybe having a condition listener but not sure if this is the way to go.
I am somewhat new to android and kotlin. Have been working with it for a month now. I dont know everything I am doing but I got given a deadline. So sadly there was no time to go and learn the basics. Thank you for any and all help. Please let me know if you need any additional code/information
my adapter
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RowViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.table_list_item, parent, false)
return RowViewHolder(itemView)
}
private fun setHeaderBg(view: View) {
view.setBackgroundResource(R.drawable.table_header_cell_bg)
}
private fun setContentBg(view: View) {
view.setBackgroundResource(R.drawable.table_content_cell_bg)
}
override fun onBindViewHolder(holder: RowViewHolder, position: Int) {
// (TableViewAdapter.DataviewHolder) .bind()
val rowPos = holder.adapterPosition
if (rowPos == 0) {
// Header Cells. Main Headings appear here
holder.itemView.apply {
setHeaderBg(txtWOrder)
setHeaderBg(txtDElNote)
setHeaderBg(txtCompany)
// setHeaderBg(txtAddress)
setHeaderBg(txtWeight)
setHeaderBg(txtbutton1)
setHeaderBg(txtbutton2)
setHeaderBg(txttvdone)
txtWOrder.text = "WOrder"
txtDElNote.text = "DElNote"
txtCompany.text = "Company"
// txtAddress.text = "Address"
txtWeight.text = "Weight"
txtbutton1.text = "Delivered"
txtbutton2.text = "Exception"
txttvdone.text = ""
}
} else {
val modal = Tripsheetlist[rowPos - 1]
holder.itemView.apply {
setContentBg(txtWOrder)
setContentBg(txtDElNote)
setContentBg(txtCompany)
// setContentBg(txtAddress)
setContentBg(txtWeight)
setContentBg(txtbutton1)
setContentBg(txtbutton2)
setContentBg(txttvdone)
txtWOrder.text = modal.WOrder.toString()
txtDElNote.text = modal.DElNote.toString()
txtCompany.text = modal.Company.toString()
// txtAddress.text = modal.Address.toString()
txtWeight.text = modal.Weight.toString()
txtbutton1.text = modal.Button1.toString()
txtbutton2.text = modal.Button2.toString()
txttvdone.text = modal.tvdone.toString()
}
}
holder.apply {
txtbutton1.setOnClickListener {
Log.e("Clicked", "Successful delivery")
txttvdone.setBackgroundResource(R.color.green)
txttvdone.setText("✓")
}
txtbutton2.setOnClickListener {
Log.e("Clicked", "Exception on delivery")
txttvdone.setBackgroundResource(R.color.orange)
txttvdone.setText("x")
}
}
}
class RowViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val txttvdone:TextView = itemView.findViewById<TextView>(R.id.txttvdone)
val txtbutton1:Button = itemView.findViewById<Button>(R.id.txtbutton1)
val txtbutton2:Button = itemView.findViewById<Button>(R.id.txtbutton2)
} class MyViewHolder(val view: View) : RecyclerView.ViewHolder(view){
var txtbutton1 = view.findViewById<Button>(R.id.txtbutton1)
val txtbutton2:Button = itemView.findViewById<Button>(R.id.txtbutton2)
var txttvdone = view.findViewById<TextView>(R.id.txttvdone)
}
I tried (TableViewAdapter.DataviewHolder) .bind() doing this and creating another class as I saw that was done in another thread(Why do values disappear after scrolling in Recycler View?) Its a lot like my problem. I just can't seem to implement his solution to make mine work. ( don't understand his solution fully)
//I am also aware that I am using android extensions which will expire at the end of the year. But for now it works and once I have the code up and running I will start to move over to the newer versions of kotlin.
A RecyclerView, as its name implies, will recycle the views when they go off screen. This means that when the view for an item comes into view, it gets recreated and the onBindViewHolder() is called to fill in the details.
Your onClickListener inside your adapter changes the background of one of the subviews for your cell view. However, that cell will be redrawn if it leaves the screen and comes back.
To get around this, your onClickListener should be changing a property on the data item, and your onBindViewHolder should check that property to determine what background color to display for the subview:
enum class DataState {
Unselected,
Success,
Failure
}
data class DataItem(var state: DataState = DataState.Unselected)
class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {
var dataItems: List<DataItem> = emptyList()
fun updateData(data: List<DataItem>) {
dataItems = data
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val dataItem = dataItems[position]
holder.txttvdone.apply {
setBackgroundResource(when (dataItem.state) {
DataState.Unselected -> android.R.color.transparent
DataState.Success -> R.color.green
DataState.Failure -> R.color.orange
})
text = when (dataItem.state) {
DataState.Unselected -> ""
DataState.Success -> "✓"
DataState.Failure -> "x"
}
}
holder.apply {
txtbutton1.setOnClickListener {
Log.e("Clicked", "Successful delivery")
dataItem.state = DataState.Success
notifyDataSetChanged()
}
txtbutton2.setOnClickListener {
Log.e("Clicked", "Exception on delivery")
dataItem.state = DataState.Failure
notifyDataSetChanged()
}
}
}
}
I have created a variable private var deals=ArrayList<Deals>() in a fragment and I have set a click listener in the onCerateView() like the following.
binding.tvAllDeals.setOnClickListener {
viewAllDeals()
}
So that it will trigger the following method
private fun viewAllDeals(){
val intent = Intent(context,ViewAllDealsActivity::class.java)
intent.putExtra("details",deals)
Log.d("Tag2", "Size is ${deals.size}")
startActivity(intent)
}
I have the following function to get the data from the firestore and then I save the result in the variable 'deals'. However, whenever I click the 'tvAllDeals' it shows many images, when I check the size of the 'deals' using Log.d 'Tag1' always shows the correct size, which is 3, whereas 'Tag2' show some random numbers like 6, 9, 24. I try to find out why this is happening but I didn't get any idea. The variable 'deals' is not used anywhere else other than declaring and initializing, to assign the value and to pass it in the 'viewAllDeals()'
private fun getDeals() {
FirestoreClass().getDeals(
onSuccess = { list ->
Result.success(list)
successDeals(list) ///// THIS FUNCTION WILL SHOW THE IMAGES IN A VIEWPAGER
deals.clear()
deals=list
Log.d("Tag1", "Size is ${deals.size}")
},
onFailure = {
}
)
}
Edit:
NOTE: 'Tag3' also shows correct array size like 'Tag1'. However,
private fun successDeals(list: ArrayList<Deals>) {
Log.d("Tag3", "Size is ${deals.size}")
if (list.size > 0) {
binding.vpDeals.visibility = View.VISIBLE
val adapter = DealsAdapter(binding.vpDeals,requireContext(), list)
binding.vpDeals.adapter = adapter
binding.vpDeals.orientation = ViewPager2.ORIENTATION_HORIZONTAL
sliderHandle= Handler()
sliderRun= Runnable {
binding.vpDeals.currentItem=binding.vpDeals.currentItem+1
}
binding.vpDeals.registerOnPageChangeCallback(
object :ViewPager2.OnPageChangeCallback(){
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
sliderHandle.removeCallbacks(sliderRun)
sliderHandle.postDelayed(sliderRun,4000)
}
}
)
} else {
binding.vpDeals.visibility = View.GONE
}
}
I have multiple option select and I need to get array of selected options but all I get is latest option selected.
Code
class PublishActivity : AppCompatActivity() {
var selectedTags: List<String>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_publish)
pTags.setOnClickListener {
var tagIds = ArrayList<String>()
val tagOptions = ArrayList<String>()
for (i in tags) {
tagOptions.add(i.title)
tagIds.add(i.id)
}
var checkedItems = ArrayList<Int>()
checkedItems.forEach{ index -> tagIds[index + 1] }
MaterialAlertDialogBuilder(this)
.setTitle(resources.getString(R.string.project_tags))
.setMultiChoiceItems(tagOptions.toTypedArray(), null) { dialog, which, checked ->
if (checked) {
checkedItems.add(which)
} else if (checkedItems.contains(which)) {
checkedItems.remove(Integer.valueOf(which))
}
// Respond to item chosen
pTags.setText("${checkedItems.size} tags selected")
}
.setPositiveButton(resources.getString(R.string.ok)) { dialog, which ->
for (i in checkedItems) {
Log.d("eeee1", tagOptions[i])
selectedTags = listOf(tagOptions[i])
}
}
.setNeutralButton(resources.getString(R.string.clear)) { dialog, which ->
pTags.text = null
pTags.hint = "0 tag selected"
if (checkedItems.size > 0) {
checkedItems.clear()
}
}
.show()
}
}
}
Log.d("eeee1", tagOptions[i]) returns such data in logcat
D/eeee1: 3D Printing
D/eeee1: 3D Architecture
D/eeee1: .NET/Mono
D/eeee1: ActionScript
but in my selectedTags I get only D/eeer1: [ActionScript]
It supposed to give me something like this D/eeer1: ["3D Printing", "3D Architecture", ".NET/Mono", "ActionScript"]
PS: what I'm actually look to achieve here is to get id of those selected items instead of their names that's why I have var tagIds = ArrayList<String>() but if that's not possible to achieve as long as it just return array of all names (like sample above) it's fine by me as well.
Any idea?
The following code sets your variable to a list with a single item. So you just overwrite your variable over and over again
selectedTags = listOf(tagOptions[i])
you need:
//Declaration
var selectedTags: MutableList<String> = mutableListOf()
...
// In loop
selectedTags.add(tagOptions[i])
You could also do it with a more functional approach:
//Declaration
var selectedTags: List<String>? = listOf()
...
// Skip the loop and use the map function
.setPositiveButton(resources.getString(R.string.ok)) { dialog, which ->
selectedTags = checkedItems.map{ tagOptions[it] }
}
To get the Id's instead of the titles you should just be able to use your tagIds instead of tagOptions. Just make sure that you get your typing right. The selectedTags list needs to be of the same type as tag.id.
You are getting only last inserted value because you are creating fresh list when ok button is clicked and assigning it to selectedTags. Problem at selectedTags = listOf(tagOptions[i]) line of your code.
Solution:
Declare a single list and put selected values into it. Like :
val selectedTags = arrayListOf<String>()
then use below code inside ok button click:
.setPositiveButton("Ok") { dialog, which ->
for (i in checkedItems) {
//selectedTags = listOf(tagOptions[i])
selectedTags.add(tagOptions[i])
}
}
I am trying to initialize my array upon user input. Say if the user enters an item into the text field and then they press the add button, I want the string from the text field to go into the array.
class CustomList : AppCompatActivity() {
lateinit var thingsList: MutableList<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_custom_list)
addItemBtn.setOnClickListener {
if(item_text_field.text.toString() == "") {
Toast.makeText(this, "Must enter text first", Toast.LENGTH_SHORT).show()
} else {
val item = item_text_field.text.toString()
thingsList = item
}
}
}
}
There's no need of lateinit here, and unless you initialize the variable you cannot add items to it. You can instead use lazy to initialize it when needed. And the MutableList.add() is used to add items to a list.
// thingsList will be initialized whenever accessed for the first time
val thingsList by lazy { mutableListOf<String>() }
addItemBtn.setOnClickListener {
if(item_text_field.text.toString() == "") {
Toast.makeText(this, "Must enter text first", Toast.LENGTH_SHORT).show()
} else {
// use add on the MutableList to add times into it
thingsList.add(item_text_field.text.toString())
}
}