I would like to start a new activity from a RecyclerView with Kotlin.
I am still exploring Kotlin and currently stuck on how to open a new activity from a RecyclerView.
class HomeScreenRecyclerAdapter : RecyclerView.Adapter<HomeScreenRecyclerAdapter.ViewHolder>()
{
private val titles = arrayOf("About Me",
"About Me", "About Me", "About Me"
)
private val details = arrayOf("Item one details", "Item two details",
"Item three details", "Item four details")
private val images = intArrayOf(R.drawable.ic_launcher_background,
R.drawable.ic_launcher_background, R.drawable.ic_launcher_background,
R.drawable.ic_launcher_background)
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder {
val v = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.main_card_view, viewGroup, false)
return ViewHolder(v)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
viewHolder.itemTitle.text = titles[position]
viewHolder.itemDetail.text = details[position]
viewHolder.itemImage.setImageResource(images[position])
}
override fun getItemCount(): Int {
return titles.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val intent: Intent? = null
var itemImage: ImageView
var itemTitle: TextView
var itemDetail: TextView
init {
itemImage = itemView.findViewById(R.id.main_image_view)
itemTitle = itemView.findViewById(R.id.main_title_view)
itemDetail = itemView.findViewById(R.id.main_description_view)
itemView.setOnClickListener {
}
}
}
}
I just cant figure out how to start a new activity for each item within the RecyclerView. I know I am making it more complicated than it is.
Just use this :
context.startActivity(Intent(context, Activity::class.java))
Just start the activity within the ViewHolder itemOnClick method as like below. And you need to pass the context reference of adapter's activity.
class HomeScreenRecyclerAdapter(var mContext:Context) : RecyclerView.Adapter<HomeScreenRecyclerAdapter.ViewHolder>()
{
private val titles = arrayOf("About Me",
"About Me", "About Me", "About Me")
private val details = arrayOf("Item one details", "Item two details",
"Item three details", "Item four details")
private val images = intArrayOf(R.drawable.ic_launcher_background,
R.drawable.ic_launcher_background, R.drawable.ic_launcher_background,
R.drawable.ic_launcher_background)
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder {
val v = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.main_card_view, viewGroup, false)
return ViewHolder(v)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
viewHolder.itemTitle.text = titles[position]
viewHolder.itemDetail.text = details[position]
viewHolder.itemImage.setImageResource(images[position])
}
override fun getItemCount(): Int {
return titles.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val intent: Intent? = null
var itemImage: ImageView
var itemTitle: TextView
var itemDetail: TextView
init {
itemImage = itemView.findViewById(R.id.main_image_view)
itemTitle = itemView.findViewById(R.id.main_title_view)
itemDetail = itemView.findViewById(R.id.main_description_view)
itemView.setOnClickListener {
mContext.startActivity(Intent(mContext, ActivityNameWhichYouWantCall::class.java))
}
}
}
}
Just try this solution:
Firstly in your adapter write listener to your recyclerview items:
class HomeScreenRecyclerAdapter(val data: ArrayList<Data>) : RecyclerView.Adapter<ContactAdapter.ViewHolder>() {
private var listener: ((Data) -> Unit)? = null
...
fun setOnItemClickListener(f: (Data) -> Unit) {
listener = f
}
And inside init :
itemView.setOnClickListener {
listener?.invoke(data[adapterPosition])
}
And Finally in your Activity you can take handle item clickListeners:
private val data = ArrayList<Data>()
val adapter = HomeScreenRecyclerAdapter(data)
adapter.setOnItemClickListener {
when(it){
1->{startActivity(Intent(context, YourActivity1::class.java))}
2->{startActivity(Intent(context, YourActivity2::class.java))}
...
else->{}
}
}
There Data is model class of your item.
That's all, I hope this help you!
Related
I saw different tutorials on the internet to create a nested recyclerView. It works as expected but all the items are collapsed when I initialize the adapter, while I need to show all of them opened:
Namely at the moment I have:
Category 1
Category 2
Category 3
Whereas I want :
Category1
Dish1
Dish2
Category2
Dish 1
Category 3
Dish 1
Dish2
I have an handle can click on every Category, for instance Category 1, and a click listener loads the adapter on the basis of the position, but I want to open all the items when the adapter is initialized
I tried also to furnish from the view that calls the Recyclerview a callback from onLayoutLoaded, but if I call the expand method, from the view it works only if i furnish one position ex. expandRow(0) For instance to open the first item, but if I call all the positions on the basis of the count, the size of the list just does not work
Here my adapter with at the end of the class attached the datamodel
class CategoryListExpandableAdapter(var context: Context, var categoriesList:MutableList<ExpandableCategoryModel>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var shouldCategoryExpanded by Delegates.notNull<Boolean>()
var presentPosition by Delegates.notNull<Int>()
init {
shouldCategoryExpanded = true
}
val parentLayout = R.layout.parent_category_item
val childLayout = R.layout.child_dish_item
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when(viewType) {
parentLayout -> {ParentCategoryViewHolder(LayoutInflater.from(parent.context).inflate(
parentLayout, parent, false))}
else -> { ChildDishViewHolder(LayoutInflater.from(parent.context).inflate(
childLayout, parent, false)) }
}
}
override fun getItemCount(): Int = categoriesList.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val row = categoriesList[position]
presentPosition = position
when(row.type){
CATEGORY_PARENT -> {
(holder as ParentCategoryViewHolder).categoryName.text = row.categoryParent.categoryName
arrowDownButtonListener(holder, row, position)
upArrowButtonListener(holder, row, position)
}
DISH_CHILD -> {
(holder as ChildDishViewHolder).descriptionDishText.text = row.dishChild.description
holder.nameDishText.text = row.dishChild.name
}
}
}
private fun upArrowButtonListener(holder: ParentCategoryViewHolder, row: ExpandableCategoryModel, position: Int) {
holder.upArrowImage.setOnClickListener {
collapseRow(position)
holder.upArrowImage.visibility = View.GONE
holder.downArrowImage.visibility = View.VISIBLE
}
}
private fun arrowDownButtonListener(holder: ParentCategoryViewHolder, row: ExpandableCategoryModel, position: Int) {
holder.downArrowImage.setOnClickListener {
holder.upArrowImage.visibility = View.VISIBLE
holder.downArrowImage.visibility = View.GONE
expandRow(position)
}
}
override fun getItemViewType(position: Int): Int {
return when (categoriesList[position].type) {
1 -> parentLayout
else -> childLayout
}
}
private fun expandRow(position: Int){
val row = categoriesList[position]
var nextPosition = position
when (row.type) {
CATEGORY_PARENT -> {
for(dishes in row.categoryParent.dishesList){
categoriesList.add(++nextPosition, ExpandableCategoryModel(DISH_CHILD, dishes))
}
notifyDataSetChanged()
}
DISH_CHILD -> {
notifyDataSetChanged()
}
}
}
private fun collapseRow(position: Int){
val row = categoriesList[position]
var nextPosition = position + 1
when (row.type) {
CATEGORY_PARENT -> {
outerloop# while (true) {
if (nextPosition == categoriesList.size || categoriesList[nextPosition].type == CATEGORY_PARENT) {
break#outerloop
}
categoriesList.removeAt(nextPosition)
}
notifyDataSetChanged()
}
}
}
class ParentCategoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
internal var categoryName : TextView = itemView.category_name
internal var downArrowImage = itemView.arrow_down
internal var upArrowImage = itemView.up_arrow
}
class ChildDishViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
internal var descriptionDishText : TextView = itemView.child_dish_item_description
internal var nameDishText = itemView.child_dish_item_name
}
}
class ExpandableCategoryModel {
lateinit var categoryParent: Category
var type : Int
lateinit var dishChild : Dish
constructor(type : Int, categoryParent: Category){
this.type = type
this.categoryParent = categoryParent
}
constructor(type : Int, categoryChild : Dish){
this.type = type
this.dishChild = categoryChild
}
}
data class CategoryList(val categories: List<Category>)
data class Category(
val categoryName: String,
val dishesList: List<Dish>
)
data class Dish(
val name: String,
val description: String
)
How to display some information from recyclerview selected item without using onClick method. When the app is started first item is selected and highlighted. I need to eg. use Toast with value of anything that is in data class. I have implemented onClick method but the question is how to do it without using this method.
This is MainActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val exampleList = generateDummyList(20)
val exampleAdapter = ExampleAdapter(getItem, exampleList)
exampleAdapter.onItemClick = { item, position: Int ->
Toast.makeText(this, "Position: $position", Toast.LENGTH_SHORT).show()
val intent = Intent(this, ItemActivity::class.java).apply {
putExtra("itempos", position)
putExtra("maxSize", maxS)
}
startActivity(intent)
}
}
}
This is adapter:
class ExampleAdapter(val chosen_item: Int, private val exampleList: List<ExampleItem>):
RecyclerView.Adapter<ExampleAdapter.ExampleViewHolder>()
{
var onItemClick: ((ExampleItem, Int) -> Unit)? = null
var selected_item: Int = chosen_item
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_recy, parent, false)
return ExampleViewHolder(itemView)
}
override fun onBindViewHolder(holder: ExampleViewHolder, position: Int){
val currentItem = exampleList[position]
holder.tv_ID.text = currentItem.id.toString()
holder.tv_NAME.text = currentItem.name
holder.tv_EMAIL.text = currentItem.email
if (position == selected_item){
holder.tv_NAME.setBackgroundColor(Color.GREEN)
} else {
holder.tv_NAME.setBackgroundColor(Color.TRANSPARENT)
}
}
override fun getItemCount(): Int {
return exampleList.size
}
inner class ExampleViewHolder(itemView:View): RecyclerView.ViewHolder(itemView) {
val tv_ID: TextView = itemView.tv_ID
val tv_NAME: TextView = itemView.tv_NAME
val tv_EMAIL: TextView = itemView.tv_EMAIL
init {
itemView.setOnClickListener{
onItemClick?.invoke(exampleList[absoluteAdapterPosition], absoluteAdapterPosition)
notifyItemChanged(selected_item)
selected_item = absoluteAdapterPosition
notifyItemChanged(selected_item)
}
itemView.isSelected
}
}
}
I have second activity - when user click on item in first activity(recyclerview) - this second activity is open - then I raise the id of item by one and open again first activity where another item is highlighted. And I need to display eg. EMAIL from ExampleItem class.
This is second activity:
class ItemActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_item)
var itempos = intent.getIntExtra("itempos",0)
val maxSize = intent.getIntExtra("maxSize",0)
button2.setOnClickListener {
if (itempos == maxSize){
itempos = itempos
} else {
itempos = itempos + 1
}
val intent = Intent(this, MainActivity::class.java).apply {
putExtra("itemposplus", itempos)
}
startActivity(intent)
}
}
}
If I understood correctly, you want to get the selected item at any time (without a click). There are several ways to do this. I recommend to you use getAdapterPosition() method in ViewHolder
First, save your ViewHolder
class ExampleAdapter(val chosen_item: Int, private val exampleList: List<ExampleItem>):
RecyclerView.Adapter<ExampleAdapter.ExampleViewHolder>()
{
var onItemClick: ((ExampleItem, Int) -> Unit)? = null
var selected_item: Int = chosen_item
lateinit var viewHolder: ExampleViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_recy, parent, false)
viewHolder = ExampleViewHolder(itemView)
return viewHolder
}
And then write a public method into the adapter for get the current item in activity
fun getCurrentItem(): ExampleItem = exampleList.get(viewHolder.getAdapterPosition())
Finally you can get selected item in activity
val selectedItem = exampleAdapter.getCurrentItem()
Also you can check getLayoutManager().findFirstVisibleItemPosition() method in your RecyclerView
I'm new to Android development. I'm trying to add a Multi countdown timer in recycler view but it does not work. Editing and deleting items in the list are okay, but I have no idea how to add a countdown timer function.
When I click the play button for starting the countdown, nothing happens. I would really appreciate it if you could tell me with a simple example.
Here is Adapter.kt.
class UserAdapter(val c: Context, val userList:ArrayList<UserData>): RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
inner class UserViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
var name: TextView
var DeleteButton: ImageView
var EditButton: ImageView
var PlayButton: ImageView
val start = 600_000L
var timer = start
lateinit var countDownTimer: CountDownTimer
init {
name = v.findViewById<TextView>(R.id.alarm_name)
DeleteButton = v.findViewById(R.id.alarm_button_delete)
EditButton=v.findViewById(R.id.alarm_button_edit)
PlayButton=v.findViewById(R.id.alarm_button_start)
DeleteButton.setOnClickListener { DeleteItem(it) }
EditButton.setOnClickListener { EditItem(it) }
PlayButton.setOnClickListener { PlayItem(it)}
}
private fun PlayItem(v: View) {
countDownTimer = object : CountDownTimer(timer,1000){
override fun onFinish() {
}
override fun onTick(millisUntilFinished: Long) {
}
}.start()
}
private fun DeleteItem(v: View) {
userList.removeAt(adapterPosition)
notifyDataSetChanged()
Toast.makeText(c, "Deleted this Information", Toast.LENGTH_SHORT).show()
}
private fun EditItem(v: View){
val position = userList[adapterPosition]
val v = LayoutInflater.from(c).inflate(R.layout.add_item,null)
val name = v.findViewById<EditText>(R.id.add_alarm_name)
AlertDialog.Builder(c)
.setView(v)
.setPositiveButton("Ok"){
dialog,_->
position.add_alram_name = name.text.toString()
notifyDataSetChanged()
Toast.makeText(c,"User Information is Edited",Toast.LENGTH_SHORT).show()
dialog.dismiss()
}
.setNegativeButton("Cancel"){
dialog,_->
dialog.dismiss()
}
.create()
.show()
true
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val inflater = LayoutInflater.from(parent.context)
val v = inflater.inflate(R.layout.list_item, parent, false)
return UserViewHolder(v)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val newList = userList[position]
holder.name.text = newList.add_alram_name
}
override fun getItemCount(): Int {
return userList.size
}
Seems like your adapter is not holding instance for each row items, as per recycling behavior the timer unable to hold the view position.
you can refer this
I have an adapter in which the items each have 3 buttons, that generate a dialog that then performs an action. I have a sense that this should be removed from the adapter (I have view models available), but it works and I am wondering: Should I move logic to the fragment, to the view model, do I need to move it at all (is the code below bad practice and if so why)? Any help/input would be greatly appreciated.
Here is the adapter code:
class ViewRecipesAdapter(val context: Context, private val recipes: List<Recipe>, private val parentFragment: Fragment) :
RecyclerView.Adapter<ViewRecipesAdapter.RecipeViewHolder>()
{
private var listToUse: List<Recipe> = recipes
private lateinit var recipesViewModel: RecipesViewModel
private var isView = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder
{
val layoutInflater = LayoutInflater.from(parent.context)
val binding: ViewRecipesItemBinding =
DataBindingUtil.inflate(layoutInflater, R.layout.view_recipes_item, parent, false)
return RecipeViewHolder(binding, context)
}
override fun getItemCount() = listToUse.size
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int)
{
val recipe = listToUse[position]
// to delete and edit items
val dao = RecipesDatabase.getInstance(context).recipeDao()
val repository = RecipeRepository(dao)
recipesViewModel = RecipesViewModel(repository)
//display data on list item
holder.bind(recipe)
Glide.with(context).load(recipe.imageOne)
.into(holder.binding.imageViewItemImage)
//tried to handle clicks here through the viewModel but I could not get it working from fragment
//the function call after viewModel calls is what works and it seems to work well
holder.binding.imageButtonItemdelete.setOnClickListener {
recipesViewModel.setIsDelete(true)
recipesViewModel.setPositionFromAdapter(position)
startDeleteDialog(position)
}
holder.binding.imageButtonItemedit.setOnClickListener {
recipesViewModel.setIsView(false)
recipesViewModel.setPositionFromAdapter(position)
isView = false
startEditOrViewDialog(position)
}
holder.binding.imageButtonItemview.setOnClickListener {
recipesViewModel.setIsView(true)
recipesViewModel.setPositionFromAdapter(position)
isView = true
startEditOrViewDialog(position)
}
}
fun setList(newList: List<Recipe>)
{
listToUse = newList
}
//dialog functions for the edit, delete, and view buttons on each item
private fun startDeleteDialog(position: Int)
{
AlertDialog.Builder(context)
.setTitle("Delete recipe?")
.setPositiveButton("Yes") { _, _ ->
recipesViewModel.deleteRecipe(recipes[position])
notifyItemRemoved(position)
}
.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}.show()
}
private fun startEditOrViewDialog(position: Int)
{
when (isView)
{
true ->
{
AlertDialog.Builder(context).setTitle("View recipe?")
.setPositiveButton("Yes") { _, _ ->
//get relevant data from current recipe
val recipe = recipes[position]
//create a dialog that shows this data in an inflated layout
val viewDialog = AlertDialog.Builder(context)
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.fragment_edit_or_view, null)
view.editText_editrecipe_directions.setText(recipe.directions)
view.editText_editrecipe_ingredients.setText(recipe.ingredients)
view.editText_editrecipe_notes.setText(recipe.notes)
view.editText_editrecipe_title.setText(recipe.title)
view.textView_date_edit.text = recipe.date
view.editText_editrecipe_title.keyListener = null
view.editText_editrecipe_directions.keyListener = null
view.editText_editrecipe_ingredients.keyListener = null
view.editText_editrecipe_notes.keyListener = null
if (recipe.rating != null)
{
view.ratingBar_edit.rating = recipe.rating
}
Glide.with(context)
.load(recipe.imageOne)
.into(view.imageView_addphoto_edit)
viewDialog.setView(view).show()
}
.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}.show()
}
false ->
{
AlertDialog.Builder(context).setTitle("Edit recipe?")
.setPositiveButton("Yes") { _, _ ->
//get relevant data from current recipe
val recipe = recipes[position]
val idString = recipe.id.toString()
recipesViewModel.setId(idString)
recipesViewModel.getRecipeById2(idString)
notifyDataSetChanged()
val controller = parentFragment.findNavController()
controller.navigate(
ViewRecipesFragmentDirections.actionNavViewrecipesToNavAddrecipe(
recipe.id.toString()
)
)
}
.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}.show()
}
}
}
override fun getItemId(position: Int): Long
{
return position.toLong()
}
override fun getItemViewType(position: Int): Int
{
return position
}
class RecipeViewHolder(val binding: ViewRecipesItemBinding, val context: Context) :
RecyclerView.ViewHolder(binding.root)
{
fun bind(recipe: Recipe)
{
if (recipe.isLeftover == true)
{
binding.tvIsLeftovers.visibility = View.VISIBLE
}
binding.textViewItemTitle.text = recipe.title
if (recipe.date != null)
{
binding.textViewItemDate.text = recipe.date
}
if (recipe.rating != null)
{
binding.ratingBar2.rating = recipe.rating
}
binding.root.animation = AlphaAnimation(0.0f, 1.0f).apply {
duration = 1000
}
}
}
}
This is the view model, with live data variables set up that I could not get working in the fragment that this RecyclerView is in:
class RecipesViewModel(private val repository: RecipeRepository) : ViewModel()
{
val recipesList = repository.getAllRecipes()
private val _isView = MutableLiveData<Boolean>()
val isView: MutableLiveData<Boolean> = _isView
private val _isEdit = MutableLiveData<Boolean>()
val isEdit: MutableLiveData<Boolean> = _isEdit
private val _positionFromAdapter = MutableLiveData<Int>()
val positionFromAdapter: MutableLiveData<Int> = _positionFromAdapter
private val _isDelete = MutableLiveData<Boolean>()
val isDelete: MutableLiveData<Boolean> = _isDelete
private val _recipesListFromSearch = MutableLiveData<List<Recipe>>()
val recipesListFromSearch: LiveData<List<Recipe>> = _recipesListFromSearch
private val _recipe = MutableLiveData<Recipe>()
val recipe: LiveData<Recipe> = _recipe
lateinit var searchString: String
val savedId = MutableLiveData<String>()
fun setPositionFromAdapter(position: Int)
{
_positionFromAdapter.value = position
}
fun setIsView(isView: Boolean)
{
_isView.value = isView
}
fun setIsDelete(isDelete: Boolean)
{
_isView.value = isDelete
}
fun setIsEdit(isEdit: Boolean)
{
_isEdit.value = isEdit
}
fun setId(id: String)
{
savedId.value = id
}
fun insertRecipe(recipe: Recipe)
{
CoroutineScope(Dispatchers.IO).launch {
repository.insertRecipe(recipe)
}
}
fun getRecipesFromQuery(query: String)
{
CoroutineScope(Dispatchers.IO).launch {
val list = repository.getRecipesSearch(query)
MainScope().launch { _recipesListFromSearch.value = list }
}
}
fun saveUserRecipeToDb(
title: String?,
ingredients: String?,
directions: String?,
notes: String?,
uriToSave: String?,
rating: Float?,
date: String?,
isLeftover: Boolean,
loadedId: String
): Boolean
{
val recipeToSave = Recipe(
title,
ingredients,
directions,
notes,
uriToSave,
null,
null,
rating,
date,
isLeftover
)
if (loadedId != "666")
{
recipeToSave.id = loadedId.toInt()
}
insertRecipe(recipeToSave)
return false
}
fun getRecipeById2(id: String) = repository.getRecipeByIdLive(id)
fun deleteRecipe(recipe: Recipe)
{
CoroutineScope(Dispatchers.IO).launch {
repository.deleteRecipe(recipe)
}
}
}
How to implement onClick in the RecyclerView. Let's assume that in Your Recycler every view is a visualization of some item and when You click on it You want to do something with that item:
Create class: ClickListener:
class ClickListener(
val clickListener: (itemId: Int) -> Unit,
)
{
fun onClick(item: ItemClass) = clickListener(item.id)
}
Now in Your RecylerViewAdapter pass as an argument this Listener:
class RecylerViewAdapter(
private val clickListener: ClickListener
)
In onBindViewHolder pass this Listenner as argument
override fun onBindViewHolder(holder: ViewHolder, position: Int)
{
holder.bind(getItem(position)!!, clickListener)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder
{
return ViewHolder.from(
parent
)
}
In Your ViewHolder class:
class ViewHolder private constructor(private val binding: ItemRecyclerBinding) :
RecyclerView.ViewHolder(binding.root)
{
companion object
{
fun from(parent: ViewGroup): ViewHolder
{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemRecyclerBinding.inflate(layoutInflater, parent, false)
return ViewHolder(
binding
)
}
}
fun bind(
item : Item,
clickListener: ClickListener
)
{
binding.item = item
binding.clickListener = clickListener
binding.executePendingBindings()
}
}
In Your item layout (which has to be converted to data binding layout) add this:
<data>
<variable
name="item"
type="com.example.sth.database.Item" /> // path to `Item`
<variable
name="clickListener"
type="com.example.sth.ui.adapter.ClickListener" /> // Path to `ClickListener`
</data>
Now You can add onClick method to Button:
android:onClick="#{() -> clickListener.onClick(item)}"
When You create Adapter in fragment or Activity You have to pass clickListenner as a parameter. In this way You can handle everything from fragment and RecyclerView doesn't care about what You do in this function.
val clickListenner = ClickListenner(
{ id -> viewModel.clickItemWithid(id) }, // click. This function from ViewModel will be executed when You click on item in recycler View
)
val adapter = RecylerViewAdapter (
clickListenner
)
This method is based on Google developers codelabs on Udacity.
Here You can check whole codelabs. It is free.
And here is just one video with implementing click listenner
These are the changes that are working for me now:
class ClickListener(val clickListener: (itemId: Int, itemPosition: Int, dialogInt: Int) -> Unit) {
fun onClickDelete(recipe: Recipe, position: Int, dialogInt: Int) = clickListener(recipe.id, position, dialogInt)
fun onClickEdit(recipe: Recipe, position: Int, dialogInt: Int) = clickListener(recipe.id, position, dialogInt)
fun onClickView(recipe: Recipe, position: Int, dialogInt: Int) = clickListener(recipe.id, position, dialogInt)
}
In the adapter:
class RecipeViewHolder private constructor(val binding: ViewRecipesItemBinding) :
RecyclerView.ViewHolder(binding.root) {
companion object {
fun from(parent: ViewGroup): RecipeViewHolder{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ViewRecipesItemBinding.inflate(layoutInflater, parent, false)
return RecipeViewHolder(binding)
}
}
fun bind(recipe: Recipe, clickListener: ClickListener) {
binding.recipe = recipe
binding.imageButtonItemdelete.setOnClickListener {
clickListener.onClickDelete(recipe, adapterPosition, 1)
}
binding.imageButtonItemedit.setOnClickListener {
clickListener.onClickEdit(recipe, adapterPosition,2)
}
binding.imageButtonItemview.setOnClickListener {
clickListener.onClickView(recipe, adapterPosition,3)
}
binding.executePendingBindings()
binding.root.animation = AlphaAnimation(0.0f, 1.0f).apply {
duration = 1000
}
}
In the fragment holding the RecyclerView :
private fun initRecyclerView() {
recipesViewModel.recipesList.observe(viewLifecycleOwner, Observer {
//update recyclerview
val list = it
listForFragment = it
clickListener = ClickListener { id, position, dialogInt ->
recipesViewModel.apply {
setPositionFromAdapter(position)
setDialogRecipe(id)
}
when (dialogInt) {
1 -> startDeleteDialog(position)
2 -> startEditDialog(position)
3 -> startViewDialog(position)
}
}
rv_viewrecipes.adapter = ViewRecipesAdapter(requireContext(), list, this, clickListener)
rv_viewrecipes.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
})
}
I couldn't figure out how to pass the position and int for the when statement through the xml onClick, but for now at least the adapter class has the view logic removed.
I am new to the Kotlin and I am trying to create an application with MVVM.
so what I am trying to do is to make a web call using retrofit in the repository and returning data to the view model and then observing the data from the fragment and as soon as data changes I am notifying it to the recycler view adapter everything is working fine.
Now, the problem is I want to update that live data of ViewHolder from the recyclerview. when I click on the CheckBox in recyclerView the data should be updated. But I don't know what is the actual way of doing this.
Here is my ViewHolder.
class MainActivityViewModel : ViewModel() {
private var mutableLiveGitUsers: MutableLiveData<ArrayList<GitUsers>>? = null
private lateinit var gitUsersRepository: GitUsersRepository
var allUsersListener: AllUsersListener? = null
fun init() {
gitUsersRepository = GitUsersRepository()
allUsersListener?.onStarted()
mutableLiveGitUsers = gitUsersRepository.getGitUsers()
allUsersListener?.onSuccess(mutableLiveGitUsers!!)
}
fun getGitUsersData(): MutableLiveData<ArrayList<GitUsers>>? {
return mutableLiveGitUsers
}
}
here is how I observe changes in fragment
mutableLiveGitUsers.observe(this, Observer {
progressBar.hide()
rvAllUsersAdapter = AllUsersAdapter(mainActivityViewModel.getGitUsersData()?.value!!)
rvAllUsers.adapter = rvAllUsersAdapter
rvAllUsersAdapter.notifyDataSetChanged()
})
RecylerView Adapter
class AllUsersAdapter(private var gitUsersArrayList: ArrayList<GitUsers>) :
RecyclerView.Adapter<AllUsersAdapter.AllUsersViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AllUsersViewHolder {
val inflater: LayoutInflater = LayoutInflater.from(parent.context)
val v: View = inflater.inflate(R.layout.items_all_users, parent, false)
return AllUsersViewHolder(v)
}
override fun getItemCount(): Int {
return gitUsersArrayList.size
}
override fun onBindViewHolder(holder: AllUsersViewHolder, position: Int) {
holder.tvUserName.text = gitUsersArrayList[position].login
Glide.with(holder.itemView.context)
.load(gitUsersArrayList[position].avatarUrl)
.centerCrop()
.into(holder.imageView)
holder.checkBox.isSelected = gitUsersArrayList.get(position).isSelected
holder.checkBox.setOnCheckedChangeListener{compoundButton, isChecked ->
if (isChecked){
/* Here I want to change the live data so I can observe that changes in my
fragment and can have an effect in UI*/
Log.d("TESTC","AllUsersAdapter IsChecked")
}else{
Log.d("TESTC","AllUsersAdapter UnChecked")
}
}
}
class AllUsersViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tvUserName: TextView = itemView.findViewById(R.id.tvUserName)
var imageView: ImageView = itemView.findViewById(R.id.imageView)
var checkBox: CheckBox = itemView.findViewById(R.id.checkBox)
}
}
Introduce an item click callback to your adapter:
class AllUsersAdapter(private var gitUsersArrayList: ArrayList<GitUsers>,
private val itemClickCallback: ((Boolean) -> Unit)?) :
RecyclerView.Adapter<AllUsersAdapter.AllUsersViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AllUsersViewHolder {
val inflater: LayoutInflater = LayoutInflater.from(parent.context)
val v: View = inflater.inflate(R.layout.items_all_users, parent, false)
return AllUsersViewHolder(v)
}
override fun getItemCount(): Int {
return gitUsersArrayList.size
}
override fun onBindViewHolder(holder: AllUsersViewHolder, position: Int) {
holder.tvUserName.text = gitUsersArrayList[position].login
Glide.with(holder.itemView.context)
.load(gitUsersArrayList[position].avatarUrl)
.centerCrop()
.into(holder.imageView)
holder.checkBox.isSelected = gitUsersArrayList.get(position).isSelected
holder.checkBox.setOnCheckedChangeListener{compoundButton, isChecked ->
if (isChecked){
/* Here I want to change the live data so I can observe that changes in my
fragment and can have an effect in UI*/
Log.d("TESTC","AllUsersAdapter IsChecked")
itemClickCallback?.invoke(true)
}else{
Log.d("TESTC","AllUsersAdapter UnChecked")
itemClickCallback?.invoke(false)
}
}
}
class AllUsersViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tvUserName: TextView = itemView.findViewById(R.id.tvUserName)
var imageView: ImageView = itemView.findViewById(R.id.imageView)
var checkBox: CheckBox = itemView.findViewById(R.id.checkBox)
}
}
Handle the click event in your fragment onViewCreated() where you initialise the adapter
val rvAdapter = AllUsersAdapter(
gitUsersArrayList = gitUsersArrayList, itemClickCallback = fun(status: Boolean) {
navController().navigate(
viewModel.updateValue(status)
)
}
)
Create the required ViewModel function:
class MainActivityViewModel : ViewModel() {
private var mutableLiveGitUsers: MutableLiveData<ArrayList<GitUsers>>? = null
private lateinit var gitUsersRepository: GitUsersRepository
var allUsersListener: AllUsersListener? = null
fun init() {
gitUsersRepository = GitUsersRepository()
allUsersListener?.onStarted()
mutableLiveGitUsers = gitUsersRepository.getGitUsers()
allUsersListener?.onSuccess(mutableLiveGitUsers!!)
}
fun getGitUsersData(): MutableLiveData<ArrayList<GitUsers>>? {
return mutableLiveGitUsers
}
fun updateValue(status: Boolean) {
//#Todo Set new value based on status received
// mutableLiveGitUsers.value =
}
}