I'm new to Kotlin. I have a MainActivity and a list view inside.
When an item is clicked in the list view, I want to update a textView inside MainActivity.
However because data should be passed from ListView to MainActivity, I did something weird:
MainActivity.kt:
data class Country(val imgRes:Int, val name:String)
class MainActivity : AppCompatActivity() {
private lateinit var binding : ActivityMainBinding
private val data = arrayListOf<Country>()
val imgRes = intArrayOf()
val data1 = arrayOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
for(i in imgRes.indices) {
val country = Country(imgRes[i], data1[i])
data.add(country)
}
val adapter = RecyclerAdapter(data, object:RecyclerAdapter.OnItemClickListener {
override fun onItemClick(v: View, pos: Int) {
binding.textView.text = (v as TextView).text
}
})
binding.recycler1.adapter = adapter
binding.recycler1.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
}
}
ListView.kt:
class RecyclerAdapter(private val dataSet:List<Country>, private val listener:OnItemClickListener) : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
public interface OnItemClickListener {
fun onItemClick(v:View, pos:Int)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = RowBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun getItemCount(): Int {
return dataSet.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
inner class ViewHolder(val binding: RowBinding) : RecyclerView.ViewHolder(binding.root) {
val rowImageView = binding.rowImageView
val rowTextView = binding.rowTextView
init {
binding.root.setOnClickListener {
listener.onItemClick(rowTextView, adapterPosition)
}
}
fun bind(pos: Int) {
with(binding) {
rowImageView.setImageResource(dataSet[pos].imgRes)
rowTextView.text = dataSet[pos].name
}
}
}
}
I declared interface(OnItemClickListener) at ListView.kt and redefine it at MainActivity.kt.
It works fine but is there any better way to do this?
You can use higher order functions instead of interface.
Define this function top of your Adapter:
var onClick: ((text: String) -> Unit)? = null
in ViewHolder:
binding.root.setOnClickListener {
onClick?.invoke(dataSet[position].text)
}
in MainActivity:
adapter.onClick = { name ->
binding.textView.text = name
}
You can also find more information about higher orders in here
<include android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="#layout/yourlayout" />
Try this in your Adapter:
var itemClickListener: ((position: Int, name: String) -> Unit)? = null
Bindviewholder:
itemClickListner.invoke(1,"anyvalue")
in Activity:
adapter.itemClickListener = {
position, name ->
Toast.makeText(requireContext(),"position is $position name is $name ",Toast.LENGTH_SHORT).show()
}
in Adapter:
private var mListener: OnItemClickListener,
interface OnItemClickListener {
fun onClick(position: Int, routerArray: ArrayList<Router>)
}
binding.yourView.setOnClickListener {
mListener.onClick(adapterPosition, routerArray)
}
in Activity:
override fun onClick(position: Int, yourArray: ArrayList<Model>) {
binding.textView.text = yourArray[position].text
}
Related
im facing errors in recyclerView here is //
data class categories(
val Name: String,
val Image:Int
)
here is model /////
object CategoriesModel {
fun getImages(): ArrayList<categories> {
val ImagesList = ArrayList<categories>()
val categoryFirst = categories(
"Memmals",R.drawable.memmals
)
ImagesList.add(categoryFirst)
val categorySecond = categories(
"Memmals",R.drawable.fish
)
ImagesList.add(categorySecond)
return ImagesList
}
}
//// adapter///
class CategoriesAdapter(private val itemList: ArrayList) :
RecyclerView.Adapter<CategoriesAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoriesAdapter.ViewHolder {
val view =LayoutInflater.from(parent.context)
.inflate(R.layout.memmals, parent,false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: CategoriesAdapter.ViewHolder, position: Int) {
holder.bind(categories("Memmals",2))
}
override fun getItemCount(): Int {
return itemList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(category: categories) {
val textViewName = itemView.findViewById<TextView>(R.id.txtCategory)
textViewName.text = category.Name
val txtImage=itemView.findViewById<View>(R.id.image)
txtImage.id=category.Image
}
}
}
/////main activity///
class MainActivity : AppCompatActivity() {
private lateinit var mAdapter: CategoriesAdapter
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding= ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.recyclerView.setOnClickListener{
val intent=Intent(this,Level1::class.java)
startActivity(intent)
}
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = GridLayoutManager(this,2)
val itemList = ArrayList<CategoriesModel>()
// from here
itemList.add(categories("Memmals",R.drawable.memmals))
itemList.add(categories("Fishes",R.drawable.fish))
itemList.add(categories("Arthropods",R.drawable.dear))
itemList.add(categories("Yalk",R.drawable.yalk))
//to here errors are there
//Adapter setting
mAdapter = CategoriesAdapter(itemList)
recyclerView.adapter = mAdapter
}
}
/// here is the error///
MainActivity.kt: (34, 22): Type mismatch: inferred type is categories but CategoriesModel was expected
MainActivity.kt
val itemList = ArrayList<CategoriesModel>()
should be changed to
val itemList = ArrayList<categories>()
because you are using CategoriesModel object and expected is categories.
I have a RecyclerView and am trying to pass the location from my adapter back to the mainactivity without success so far .
I think the code in the adapter is passing the data back is working, but unfortunately I can’t work out how to set the initialisation in the onCreate in the MainActivity to work? I’m not certain how to call this using “lazy” (ie “private val itemAdapter by lazy” in the mainactivity”)?
I am getting frustrated solving this so any help appreciated.
MainActivity.kt
class MainActivity : AppCompatActivity(), ItemAdapter.OnItemClickListener {
private val itemAdapter by lazy {
ItemAdapter { position: Int, item: Item ->
Toast.makeText(this#MainActivity, "Pos ${position}", Toast.LENGTH_LONG).show()
item_list.smoothScrollToPosition(position)
} }
private val possibleItems = listOf(
Item("Plane 1", R.drawable.ic_airplane),
Item("Car 2", R.drawable.ic_car),
Item("Meals 3", R.drawable.ic_food),
Item("Fuel 4", R.drawable.ic_gas),
Item("Home 5 ", R.drawable.ic_home)
)
fun onImageClick(imageData: String?) {
// handle image data
}
override fun onCreate(savedInstanceState: Bundle?) {
var position: Int
val thisitem: Item
var thisunit: Unit
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// var itemAdapter = ItemAdapter(possibleItems)
// var thisitemAdapter = ItemAdapter((position, thisitem) -> thisunit)
// Toast.makeText(this#MainActivity, "Pos ${position}", Toast.LENGTH_LONG).show()
// item_list.smoothScrollToPosition(position) //CAN'T GET THIS TO COMPILE
item_list.initialize(itemAdapter)
itemAdapter.setOnItemClickListener(this) // Added this
ItemSnapHelper().attachToRecyclerView(item_list)
item_list.setViewsToChangeColor(listOf(R.id.list_item_background, R.id.list_item_text))
item_list.scrollToPosition(Int.MAX_VALUE/2)
itemAdapter.setItems(possibleItems)
}
override
public fun getAdapterPosition(position : Int ){
Toast.makeText(this,"this is toast message" + position,Toast.LENGTH_SHORT).show()
}
private fun getLargeListOfItems(): List<Item> {
val items = mutableListOf<Item>()
(0..40).map { items.add(possibleItems.random()) }
return items
}
}
data class Item(
val title: String,
#DrawableRes val icon: Int
)
ItemAdapter.kt
class ItemAdapter(val itemClick: (position:Int,item:Item) -> Unit) : RecyclerView.Adapter<ItemViewHolder>() {
private var items: List<Item> = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder =
ItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false))
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val pos = position % items.size
holder.bind(items[pos]) // bind the actual item
Log.d("xx position= ", pos.toString())
}
override fun getItemCount(): Int = Int.MAX_VALUE
fun setItems(newItems: List<Item>) {
items = newItems
notifyDataSetChanged()
}
lateinit var listener: OnItemClickListener
public interface OnItemClickListener {
fun getAdapterPosition(position : Int )
}
public fun setOnItemClickListener(listener: OnItemClickListener) {
this.listener= listener
}
}
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: Item) {
view.list_item_text.text = "${item.title}"
view.list_item_icon.setImageResource(item.icon)
}
}
you have a listener so you can write like this
class MainActivity : AppCompatActivity(), ItemAdapter.OnItemClickListener {
private lateinit var itemAdapter: ItemAdapter
private val possibleItems = listOf(
Item("Plane 1", R.drawable.ic_airplane),
Item("Car 2", R.drawable.ic_car),
Item("Meals 3", R.drawable.ic_food),
Item("Fuel 4", R.drawable.ic_gas),
Item("Home 5 ", R.drawable.ic_home)
)
fun onImageClick(imageData: String?) {
// handle image data
}
override fun onCreate(savedInstanceState: Bundle?) {
var position: Int
val thisitem: Item
var thisunit: Unit
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
itemAdapter = ItemAdapter()
itemAdapter.listener = this
item_list.initialize(itemAdapter)
ItemSnapHelper().attachToRecyclerView(item_list)
item_list.setViewsToChangeColor(listOf(R.id.list_item_background, R.id.list_item_text))
item_list.scrollToPosition(Int.MAX_VALUE/2)
itemAdapter.setItems(possibleItems)
}
override
public fun getAdapterPosition(position : Int ){
Toast.makeText(this,"this is toast message" + position,Toast.LENGTH_SHORT).show()
}
private fun getLargeListOfItems(): List<Item> {
val items = mutableListOf<Item>()
(0..40).map { items.add(possibleItems.random()) }
return items
}
}
ItemAdapter.kt
class ItemAdapter : RecyclerView.Adapter<ItemViewHolder>() {
private var items: List<ClipData.Item> = listOf()
var listener: OnItemClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder = ItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false))
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(items[position], position)
}
override fun getItemCount(): Int = Int.MAX_VALUE
fun setItems(newItems: List<ClipData.Item>) {
items = newItems
notifyDataSetChanged()
}
}
class ItemViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: ClipData.Item, position: Int) {
view.list_item_text.text = "${item.title}"
view.list_item_icon.setImageResource(item.icon)
view.list_item_text.setOnClickListener {
listener?.getAdapterPosition(position)
}
}
}
interface OnItemClickListener {
fun getAdapterPosition(position: Int)
}
Instead of creating an interface you can also pass a lambda function as a callback to the Adapter.
Basic structure will be like this:
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
val itemAdapter = ItemAdapter { position ->
// do whatever you wish with this position
}
recyclerView.adapter = itemAdapter
...
}
}
class ItemAdapter(private val onClick: (Int) -> Unit) {
...
inner class ItemViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: ClipData.Item, position: Int) {
...
view.setOnClickListener {
onClick(position)
}
}
}
}
If you don't want to create ItemViewHolder as an inner class, you can accept the onClick lambda in its constructor and pass it there from onCreateViewHolder.
I'm trying to update the below recycler view to show the new data that input from the form in the second activity. However, its only showing the original list that I had in there. What am I doing wrong here?
Here is the main activity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
var workoutList = mutableListOf(
Workout("a","d","d","d"),
Workout("a","d","d","d"),
Workout("a","d","d","d")
)
val adaptor = WorkoutAdaptor(workoutList)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnNext.setOnClickListener {
Intent(this, SecondActivity::class.java).also {
startActivity(it)
}
}
binding.recyclerView.adapter = adaptor
binding.recyclerView.layoutManager = LinearLayoutManager(this)
}
override fun onRestart() {
super.onRestart()
var workout = intent.getSerializableExtra("EXTRA_WORKOUT") as Workout
workoutList.add(workout)
adaptor.notifyDataSetChanged()
}
}
Here is the adapter:
class WorkoutAdaptor (
var workouts: List<Workout>
) : RecyclerView.Adapter<WorkoutAdaptor.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val workoutCardBinding = WorkoutCardBinding.inflate(
LayoutInflater.from(parent.context), parent, false)
return ViewHolder(workoutCardBinding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(workouts[position])
}
override fun getItemCount(): Int {
return workouts.size
}
inner class ViewHolder(private val workoutCardBinding: WorkoutCardBinding) :
RecyclerView.ViewHolder(workoutCardBinding.root) {
fun bind(workout: Workout) {
workoutCardBinding.tvWorkoutCard.text = workout.toString()
}
}
}
And here is the activity where I get the workout object from:
class SecondActivity : AppCompatActivity() {
private lateinit var binding: ActivitySecondBinding
lateinit var spWorkoutsPosition: String
lateinit var spIncrementsPosition: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySecondBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.spWorkoutTypes.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
spWorkoutsPosition = p0?.getItemAtPosition(p2) as String
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
binding.spIncrements.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>?, view: View?, position: Int, id: Long) {
spIncrementsPosition = adapterView?.getItemAtPosition(position) as String
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
binding.btnSave.setOnClickListener {
val workoutName = binding.tvWorkoutName.text.toString()
val workoutType = spWorkoutsPosition.toString()
val workoutIncrement = spIncrementsPosition.toString()
val workoutRestTime = binding.etRestTime.text.toString()
val workout = Workout(workoutName, workoutType, workoutIncrement, workoutRestTime)
Intent(this, MainActivity::class.java).also {
it.putExtra("EXTRA_WORKOUT", workout)
startActivity(it)
}
}
}
}
As #John Doe commented you should probably use ActivityResultLauncher. refer here.
and as for the update issue the recommended way of implementing recyclerView's adapter is androidx.recyclerview.widget.ListAdapter. The ListAdapter handles addition and removal without the need to redraw the entire view, and even animate those changes.
here's the implementation for your adapter:
class WorkoutAdaptor :
ListAdapter<Workout, WorkoutAdaptor.ItemViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(
ItemAutocompleteSearchResultBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(position)
}
inner class ItemViewHolder(val binding: ItemAutocompleteSearchResultBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int) {
binding.apply {
workoutCardBinding.tvWorkoutCard.text = getItem(position).toString()
}
}
}
private class DiffCallback : DiffUtil.ItemCallback<Workout>() {
override fun areContentsTheSame(oldItem: Workout, newItem: Workout): Boolean {
return oldItem == newItem
}
override fun areItemsTheSame(oldItem: Workout, newItem: Workout): Boolean {
return oldItem.param1 == newItem.param1 &&
oldItem.param2 == newItem.param2 &&
oldItem.param3 == newItem.param3 &&
oldItem.param4 == newItem.param4
}
}
}
then you only have to call,
adapter.submitList(workoutList)
after you update the workoutList and the ListAdapter handles the rest.
and also call the adapter.submitList() in onStart() or onResume() methods.
How do I return the result of the position from my Recyclerview Adapter, back to the
MainActivity.kt?
Any help on how to achieve this in Kotlin would be really appreciated!
Thanks
Adapter.kt
class UsersAdapter(
private val users: ArrayList<User>
) : RecyclerView.Adapter<UsersAdapter.DataViewHolder>() {
class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(user: User) {
itemView.textViewUserName.text = user.name
Glide.with(itemView.imageViewAvatar.context)
.load(user.avatar)
.into(itemView.imageViewAvatar)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
DataViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_layout, parent,
false
)
)
override fun getItemCount(): Int = Int.MAX_VALUE
override fun onBindViewHolder(holder: DataViewHolder, position: Int) {
val pos = position % users.size
holder.bind(users[pos]) // bind the actual item
Log.d(Constraints.TAG, " onBindViewHolder:" + pos)
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var adapter: ConcatAdapter
lateinit var userVerticalAdapter: UsersAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupDataInRecyclerView()
}
private fun setupDataInRecyclerView() {
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,
false)
userVerticalAdapter = UsersAdapter(DataSource.getUser())
val listOfAdapters = listOf(userVerticalAdapter)
adapter = ConcatAdapter(listOfAdapters)
recyclerView.adapter = adapter
recyclerView.scrollToPosition(Int.MAX_VALUE/2)
ItemSnapHelper().attachToRecyclerView(recyclerView)
}
You need to add callback to your adapter -
lateinit var listener: OnItemClickListener
public interface OnItemClickListener {
fun getAdapterPosition(position : Int )
}
fun setOnItemClickListener(listener: OnItemClickListener) {
this.listener= listener
}
Call listener's method form where you want to get value of quantity as below -
listener?.getAdapterPosition(pos)
Implement this listener in your activity -
class MainActivity : AppCompatActivity(), RecycleViewCartAdapter
.OnItemClickListener {
override
public fun getAdapterPosition(position : Int ){
// required value is in the position variable
}
}
Add below line above setAdapter method
adapter.setOnItemClickListener(this)
recyclerView.adapter = adapter
Second Activity
class BookDescription : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_book_description)
var books = intent.getParcelableExtra("String") as Book
Glide.with(this).load(books.imageUrl).into(bookImg1)
nameTxt1.text = books.name
autorTxt1.text = books.name
} }
Adapter class
class Adapter(private val context: Context) :
RecyclerView.Adapter<Adapter.Holder>() {
private var datalist = mutableListOf<Book>()
fun setListdata(data: MutableList<Book>){
datalist = data
}
inner class Holder(itemView : View) : RecyclerView.ViewHolder(itemView){
fun bindView(book: Book){
Glide.with(context).load(book.imageUrl).into(itemView.bookImg)
itemView.nameTxt.text = book.name
itemView.autorTxt.text= book.writer
itemView.bookImg.setOnClickListener(
View.OnClickListener {
val intent = Intent(context, BookDescription::class.java)
intent.putExtra("jbg", book)
context.startActivity(intent)
}
)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = LayoutInflater.from(context).inflate(R.layout.book_format, parent,
false )
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val book = datalist[position]
holder.bindView(book)
}
override fun getItemCount(): Int {
return if (datalist.size> 0){
datalist.size
}else{
0
}
} }
Main Activity
class MainActivity : AppCompatActivity() {
private lateinit var adapter : Adapter
private val viewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java)}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
adapter = Adapter(this)
recycle.layoutManager = GridLayoutManager(this,2)
recycle.adapter = adapter
observerData()
}
fun observerData(){
viewModel.fetchUserData().observe(this,Observer{
adapter.setListdata(it)
adapter.notifyDataSetChanged()
})
}
}
Class Book
#Parcelize data class Book(val imageUrl:String= "URL IMmage",
val name:String = "Naziv knjige",
val writer:String= "Pisac knjige") :Parcelable!
*[Logcat]**(https://i.stack.imgur.com/Wnt2O.jpg)
You need to make sure that your Book class implements Parcelable, and then make sure that the key used to put the extra matches the key used to retrieve it:
Adapter
val intent = Intent(context, BookDescription::class.java)
intent.putParcelable("UNIQUE_KEY", book)
context.startActivity(intent)
BookDescription
var books = intent.getParcelableExtra("UNIQUE_KEY") as Book