I'm starting using Kotlin (i'm a web dev) to maintain the mobile app of my current job. To practice my learning, I'm creating a basic app which is displaying a list of France departments (using a REST Api), and I need to allow the user to click on a list item to get more info on the selected item.
I'm trying to build this with databinding, Koin as dependency injection, and Room as db layer.
My issue is that I created a RecyclerView custom Adapter, and used the databinding to give it the datas. But now I want to implement the onClick behaviour, which should launch another activity to display item details. My problem is: I don't know how to do this in a clean way.
I was thinking about creating a viewModel to link to my Adapter, but can't really find how to do it well. And even if I did, how to start another activity in a viewModel ? (don't have access to the context and startActivity function). So I finally dropped that solution that doesn't seems to fit.
So I'm currently thinking of passing directly from my adapter the onClick function, but can't find a way to bind this function in my xml file. Here is my files:
MainActivity:
class MainActivity : AppCompatActivity() {
private val mViewModel: DepartmentsViewModel by viewModel()
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.toolbar.title = "Liste des départements"
val adapter = DepartmentListAdaptater()
binding.recyclerview.adapter = adapter
binding.recyclerview.layoutManager = LinearLayoutManager(this)
mViewModel.allDepartments.observe(this, Observer { data -> adapter.submitList(data) })
}
}
RecyclerView.Adapter:
class DepartmentListAdaptater : RecyclerView.Adapter<DepartmentListAdaptater.ViewHolder>() {
private var dataSet: List<Department>? = null
inner class ViewHolder(private val binding: DepartmentListRowBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(department: Department?) {
binding.department = department
}
}
fun submitList(list: List<Department>) {
dataSet = list
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = DepartmentListRowBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun getItemCount(): Int = dataSet?.size ?: 0
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(dataSet?.get(position))
}
}
The XML View:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="department" type="com.navalex.francemap.data.entity.Department" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="72dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="#drawable/list_item_bg"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:clickable="true"
tools:ignore="UselessParent">
<TextView
android:id="#+id/textView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingRight="16dp"
android:text="#{department.nom}"
android:paddingLeft="16dp"/>
</LinearLayout>
</RelativeLayout>
</layout>
First I want to say that it's really impressive that you are a web developer and you already have a lot of knowledge about things like dependency injection and keep the state of the view on ViewModel, congrats. Now, let's talk about your problem... I'll start with some suggestions that will improve the code clarity and performance.
For the Adapter implementation, always prefer to use ListAdapter, because this implementation have a more efficient way to compare the current list with the new list and update it. You can follow this example:
class MyAdapter: ListAdapter<ItemModel, MyAdapter.MyViewHolder>(DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = FragmentFirstBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(getItem(position))
}
class MyViewHolder(
private val binding: FragmentFirstBinding
): RecyclerView.ViewHolder(binding.root) {
fun bind(item: ItemModel) {
// Here you can get the item values to put this values on your view
}
}
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<ItemModel>() {
override fun areItemsTheSame(oldItem: ItemModel, newItem: ItemModel): Boolean {
// need a unique identifier to have sure they are the same item. could be a comparison of ids. In this case, that is just a list of strings just compare like this below
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: ItemModel, newItem: ItemModel): Boolean {
// compare the objects
return oldItem == newItem
}
}
}
}
In your fragment, you have a observer, that observe the value you want to sent to the adapter, right? When a update happen, you call the submitList sending the updated list and when the adapter receive this new list, the adapter will be responsible to update just the items that changed, because of your DIFF_CALLBACK implementation.
About the onClick item, you can wait for a callback on your adapter. Doing this:
class MyAdapter(
private val onItemClicked: (item: ItemModel) -> Unit
): ListAdapter<ItemModel, MyAdapter.MyViewHolder>(DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = FragmentFirstBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MyViewHolder(binding, onItemClicked)
}
// ...
class MyViewHolder(
private val binding: FragmentFirstBinding,
private val onItemClicked: (item: ItemModel) -> Unit
): RecyclerView.ViewHolder(binding.root) {
fun bind(item: ItemModel) {
// ...
// Here you set the callback to a listener
binding.root.setOnClickListener {
onItemClicked.invoke(item)
}
}
}
// ...
}
As you can see, we will receive the callback on the Adapter constructor, then we send to the ViewHolder by constructor too. And on the ViewHolder bind we set the callback to a click listener.
On you fragment, you will have something like this:
class MyFragment: Fragment() {
private lateinit var adapter: MyAdapter
private val onItemClicked: (itemModel: ItemModel) -> Unit = { itemModel ->
// do something here when the item is clicked, like redirect to another activity
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = MyAdapter(onItemClicked)
}
}
I hope it helps you. Please, let me know if you need something more. I really appreciate helping.
I don't know about data binding specifically, but a typical way to do it is to let the Activity handle details like app navigation, and let the Adapter trigger that logic. A listener function is an easy way to do this:
// in your Adapter
var clickListener: ((YourData) -> ())? = null
// in your ViewHolder (make it an inner class so it can access the Adapter's
// fields, like the listener object and the stored data)
init {
clickableView.setOnClickListener {
// pass back whatever data here, if the listener needs to know
// what's been clicked. I'm just doing a lookup and passing
// the data item currently being displayed
clickListener?.invoke(
adapterData[bindingAdapterPosition]
)
}
}
// in your Activity, when setting up the adapter
adapter.clickListener = { whateverData ->
// do what you need to do in response to the click
}
So the Activity itself is defining that logic about actions that should be taken when a click happens - it's basically wiring up different parts of the app, so the Adapter doesn't need to be concerned with anything except taking data, displaying it, and informing a listener when specific interactions take place. That listener code (defined by the Activity) could navigate somewhere else, or update a database, or pass it to a networking component... the adapter doesn't need to know about that.
(The non-Kotlin way to do this would be to create an interface and have the Activity implement that, and pass itself as the listener/callback object, that kind of thing)
Related
Faced such a problem: I have a RecyclerView, the data for which I get from the ViewModel using StateFlow:
viewModel.items.collect {
setRecyclerView(items)
}
Then, let's say, somewhere inside the Fragment, I change the data for items and there are more of them. In order for my RecyclerView to see my changes, I have to call the setRecyclerView(items) function again, which, it seems to me, can lead to the most unexpected consequences (for example, items will be duplicated). The question is: is it possible to somehow change the data and update the RecyclerView (including the onBindViewHolder function in it) without yet another creation of an Adapter?
Let's start talking about the adapter implementation. Reading your question, I believe you used RecyclerView.Adapter to implement your adapter. But there is another option that is simpler and more performant than this. It's the ListAdapter:
The most interesting thing about ListAdapter is the DiffUtil, that have a performative way to check if any item on your list was updated, deleted, or included. Here's a sample of the implementation:
abstract class MyAdapter: ListAdapter<ItemModel, MyAdapter.MyViewHolder>(DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = ItemSimplePosterBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(getItem(position))
}
class MyViewHolder(
private val binding: ItemSimplePosterBinding
): RecyclerView.ViewHolder(binding.root) {
fun bind(item: ItemModel) {
// Here you can get the item values to put these values on your view
}
}
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<ItemModel>() {
override fun areItemsTheSame(oldItem: ItemModel, newItem: ItemModel): Boolean {
// need a unique identifier to have sure they are the same item. could be a comparison of ids. In this case, that is just a list of strings just compares like this below
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: ItemModel, newItem: ItemModel): Boolean {
// compare the objects
return oldItem == newItem
}
}
}
}
So, when your list is updated, you just have to call the submitList from the adapter, like this:
viewModel.items.collectLatest { items ->
// You will send the items to your adapter here
adapter.submitList(items)
}
Then, your RecyclerView just has to be configured on onViewCreated for example, and your list can be defined and updated in another place, observing the items change from ViewModel.
I am getting data from API http and using dataBinding and viewModel, all works but when I try to convert my list to mutableList and add All don't getting nothing, also try pass the data of simple way and working , the data is be there, the problem is that notifyDataSetChanged() don't do changes in my Adapter and I dont see nathing in the recyclerView.
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MostPopularTVShowsViewModel
private lateinit var activityMainBinding : ActivityMainBinding
private var tvShows: List<TVShow> = ArrayList()
private lateinit var tvShowAdapter:TVShowsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main)
doInitialization()
}
private fun doInitialization(){
activityMainBinding.tvShowRecycleView.setHasFixedSize(true)
viewModel= ViewModelProvider(this).get(MostPopularTVShowsViewModel::class.java)
tvShowAdapter= TVShowsAdapter(tvShows)
activityMainBinding.tvShowRecycleView.adapter=tvShowAdapter
getMostPopularTVShows()
}
private fun getMostPopularTVShows(){
activityMainBinding.isLoading=true
viewModel.getMostPopularTVShows(0).observe(this, { mostPopularTVShowsResponse ->
activityMainBinding.isLoading=false
if (mostPopularTVShowsResponse != null){
tvShows.toMutableList().addAll(mostPopularTVShowsResponse.tvShows)
//tvShows=mostPopularTVShowsResponse.tvShows
println("size is: "+tvShows.size)
tvShowAdapter.notifyDataSetChanged()
}else{
Toast.makeText(this," NULL", Toast.LENGTH_SHORT).show()
}
})
}
}
when i change tvShows=mostPopularTVShowsResponse.tvShows insted tvShows.toMutableList().addAll(mostPopularTVShowsResponse.tvShows) the list gets the data but notifyDataSetChanged dont working
this is my adapter
class TVShowsAdapter(private val items: List<TVShow>): RecyclerView.Adapter<TVShowsAdapter.ViewHolder>(){
private lateinit var layoutInflater: LayoutInflater
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
layoutInflater= LayoutInflater.from(parent.context)
val binding: ItemContainerTvShowBinding = DataBindingUtil.inflate(
layoutInflater, R.layout.item_container_tv_show,parent,false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int)= holder.bind(items[position])
override fun getItemCount()= items.size
class ViewHolder(private val itemContainerTvShowBinding: ItemContainerTvShowBinding):
RecyclerView.ViewHolder(itemContainerTvShowBinding.root){
fun bind (tvShow: TVShow){
itemContainerTvShowBinding.tvShow=tvShow
itemContainerTvShowBinding.executePendingBindings()
}
}
}
You're not actually changing the data in the adapter. You're passing in a reference to the tvShows ArrayList when you create the adapter, so if you changed the contents of that list the adapter would be able to see it. But when you do
tvShows.toMutableList().addAll(mostPopularTVShowsResponse.tvShows)
you're creating a new, separate list by calling toMutableList, and you're adding stuff to that. tvShows is still the original, immutable ArrayList with nothing in it.
If you're going to do it this way, you need to make the list mutable from the beginning
val tvShows = mutableListOf<TVShow>()
then you can clear and add to it when you get new data.
Generally though, a better idea is to give the adapter some kind of setData function where you pass in a list, so it can update its own internal data set and notify itself about the change. That way the adapter manages it state itself, all your activity or fragment needs to do is pass it some new data
I want to set an onclicklistener in the onBindViewHolder in order to navigate to a different fragment and send along some data to that fragment.
For the life of me, I can't seem to find a way to make it work. Any and all help is greatly appreciated!
The adapter class:
class ListAdapter(private val list: List<Workout>): RecyclerView.Adapter<WorkoutViewHolder>() {
override fun getItemCount(): Int{
return list.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WorkoutViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return WorkoutViewHolder(layoutInflater, parent)
}
override fun onBindViewHolder(holder: WorkoutViewHolder, position: Int) {
val workout: Workout = list[position]
holder.itemView.setOnClickListener{
Toast.makeText(holder.itemView.context, "TEST", Toast.LENGTH_LONG).show()
val id = workout.workoutId
val bundle = Bundle()
bundle.putInt("workoutId", id)
Navigation.createNavigateOnClickListener(R.id.workoutDetailsFragment)
}
holder.bind(workout)
}
}
I can get the toast to pop up, so the onclicklistener seems to be working. However, the navigation part does not work.
If I just set a button inside the fragment that is hosting the recyclerview and add button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.workoutDetailsFragment)) it can navigate just fine. So the problem seems to be calling the navigate function from inside the onclicklistener inside the onbindviewholder
Navigation.createNavigateOnClickListener() creates an OnClickListener. Creating an OnClickListener just to never set it on anything doesn't do anything.
Instead, you'll want to just trigger your navigate() call directly, doing the same one line of code that createNavigateOnClickListener does internally:
override fun onBindViewHolder(holder: WorkoutViewHolder, position: Int) {
val workout: Workout = list[position]
holder.itemView.setOnClickListener{
Toast.makeText(holder.itemView.context, "TEST", Toast.LENGTH_LONG).show()
val id = workout.workoutId
val bundle = Bundle()
bundle.putInt("workoutId", id)
// Using the Kotlin extension in the -ktx artifacts
// Alternatively, use Navigation.findNavController(holder.itemView)
holder.itemView.findNavController().navigate(
R.id.workoutDetailsFragment, bundle)
}
holder.bind(workout)
}
You need to assign your created listener rather than using it inside of a lambda. When you use a lambda with setOnClickListener(), the lambda literally is your listener. So in your example, you're creating a listener, but it's never assigned anywhere.
So to instead assign the created listener from Navigation.createNavigateOnClickListener(), your code should look like holder.itemView.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.workoutDetailsFragment))
I have a RecyclerView which was build using an Arraylist. That Arraylist consists of User defined objects named ListItem.
Each recyclerview has a card view. Each CardView holds each ListItem.
I have removed one CardView from that RecyclerView.
When I rotate the screen , A new Activity is created which results in showing the old data. But I want the recyclerview to hold only updated list and should retain the scrolled position.
ListItem class :
class ListItem(var title: String, var info: String, val imageResource: Int) {
}
MainActivity class :
class MainActivity : AppCompatActivity() {
private lateinit var mSportsData: ArrayList<ListItem>
private lateinit var mAdapter: MyAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val gridColumnCount = resources.getInteger(R.integer.grid_column_count)
recycler_view.layoutManager = GridLayoutManager(this,gridColumnCount)
mSportsData = ArrayList()
recycler_view.setHasFixedSize(true)
initializeData()
recycler_view.adapter = mAdapter
var swipeDirs = 0
if (gridColumnCount <= 1) {
swipeDirs = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
}
val helper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT or ItemTouchHelper.UP or ItemTouchHelper.DOWN,swipeDirs) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
val from = viewHolder.adapterPosition
val to = target.adapterPosition
Collections.swap(mSportsData,from,to)
mAdapter.notifyItemMoved(from,to)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
mSportsData.removeAt(viewHolder.adapterPosition)
mAdapter.notifyItemRemoved(viewHolder.adapterPosition)
}
})
helper.attachToRecyclerView(recycler_view)
}
private fun initializeData() {
val sportsList : Array<String> = resources.getStringArray(R.array.sports_titles)
Log.d("Printing","$sportsList")
val sportsInfo : Array<String> = resources.getStringArray(R.array.sports_info)
val sportsImageResources : TypedArray = resources.obtainTypedArray(R.array.sports_images)
mSportsData.clear()
for (i in sportsList.indices-1) {
Log.d("Printing","${sportsList[i]},${sportsInfo[i]},${sportsImageResources.getResourceId(i,0)}")
mSportsData.add(ListItem(sportsList[i], sportsInfo[i], sportsImageResources.getResourceId(i, 0)))
}
sportsImageResources.recycle()
mAdapter = MyAdapter(mSportsData,this)
mAdapter.notifyDataSetChanged()
}
fun resetSports(view: View) {
initializeData()
}
}
MyAdapter class :
class MyAdapter(var mSportsData: ArrayList<ListItem>, var context: Context) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.wordlist_item,parent,false))
}
override fun getItemCount() = mSportsData.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val listItem = mSportsData.get(position)
holder.bindTo(listItem)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
override fun onClick(view: View) {
val currentSport = mSportsData.get(adapterPosition)
val detailIntent = Intent(context, DetailActivity::class.java)
detailIntent.putExtra("title", currentSport.title)
detailIntent.putExtra("image_resource", currentSport.imageResource)
context.startActivity(detailIntent)
}
fun bindTo(currentSport : ListItem){
itemView.heading_textview.setText(currentSport.title)
itemView.description_textview.setText(currentSport.info)
Glide.with(context).load(currentSport.imageResource).into(itemView.image_view)
}
}
}
You can restrict activity restarting in your Manifest if you have same layout for Portrait and Landscape mode.
Add this to your activity in the manifest.
<activity android:name=".activity.YourActivity"
android:label="#string/app_name"
android:configChanges="orientation|screenSize"/>
If you don't want to restrict screen orientation changes, then you can use OnSaveInstanceState method to save your older data when orientation changed. Whatever data you save via this method you will receive it in your OnCreate Method in bundle. Here is the helping link. So here as you have ArrayList of your own class type you also need to use Serializable or Parcelable to put your ArrayList in your Bundle.
Except these making ArrayList as public static is always a solution, But its not a good solution in Object Oriented paratime. It can also give you NullPointerException or loss of data, in case of low memory conditions.
It looks like initializeData is called twice since onCreate is called again on orientation change, you could use some boolean to check if data has been already initialized then skip initializing
What you are doing is you are deleting the values that are passed down to the recyclerview but when the orientation changes the recyclerview reloads from activity and the original data from activity is passed down again and nothing changes, so if you want to save the changes in recyclerview you have to change the original data in the activity so that if the view reloads the data is the same.
I think u initialize adapter in oncreate method in which the whole adapter will be recreated and all datas is also newly created when configuration changes. Because u init data in oncreate method. Try something globally maintain the list and also delete the item in the list in activity when u delete in adapter also. Or try something like view model architecture
Use MVVM pattern in the project. It will manage the orientation state.
MVVM RecyclerView example:
https://medium.com/#Varnit/android-data-binding-with-recycler-views-and-mvvm-a-clean-coding-approach-c5eaf3cf3d72
I'm attempting to write a basic Todo list in Kotlin but wanted to use the recommended best practices and Android architecture components. At this point I have the basic architecture set up and there is a RecyclerView list of items stored in the database with a checkbox on the left side, and a description to the right. So far the list automatically updates when new data is added (via the floating action button). Now I want to update the record immediately whenever the checkbox is clicked for a particular item.
I can't figure out how or where to set the checkbox listener in order to pass the checked state and the item id to the ViewModel in order to update the data in the database. I thought about defining the listener directly inside the adapter, but then I can't find any way to call my ViewModel's update method. But then if I set up the listener in the fragment and pass that to the adapter, I can't find a way to get the id of the item.
todo list screenshot
This is my current fragment:
class ChecklistFragment : Fragment() {
private lateinit var checklistViewModel: ChecklistViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_checklist, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Set up RecyclerView
items_list.layoutManager = LinearLayoutManager(activity)
val adapter = ToDoItemAdapter(object: CompoundButton.OnCheckedChangeListener {
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
Toast.makeText(activity, "checked", Toast.LENGTH_SHORT).show()
}
})
items_list.adapter = adapter
// Set up ViewModel
checklistViewModel = ViewModelProviders.of(this).get(ChecklistViewModel::class.java)
checklistViewModel.allToDoItems.observe(this, Observer { toDoItems ->
toDoItems?.let { adapter.setToDoItems(it) }
})
// Set up fab
add_list_item_fab.setOnClickListener {
checklistViewModel.insert(ToDoItem(description = "Item ${Random.nextInt(1,999)}", checked = Random.nextBoolean()))
}
}
}
This is my current adapter:
class ToDoItemAdapter(val onCheckedChangeListener: CompoundButton.OnCheckedChangeListener) : RecyclerView.Adapter<ToDoItemAdapter.ViewHolder>() {
private var toDoItems = emptyList<ToDoItem>()
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val checkbox: CheckBox = view.checkbox
val tvDescription: TextView = view.tv_description
fun bind(position: Int) {
checkbox.isChecked = toDoItems[position].checked
checkbox.setOnCheckedChangeListener(onCheckedChangeListener)
tvDescription.text = toDoItems[position].description
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.checklist_item, parent, false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount() = toDoItems.size
internal fun setToDoItems(toDoItems: List<ToDoItem>) {
this.toDoItems = toDoItems
notifyDataSetChanged()
}
}
What is a best practice approach to listening to checked items and immediately storing those changes in the database using the MVVM architecture?
If you want to click or check something in RecyclerView Item,
There is a elegant way for that.
class MyAdapter(val viewModel : ViewModel) : RecyclerView.ViewModel<ViewHolder>{
fun onCreateViewModel(..){
val binding = ItemRecyclerViewBinding.inflate(LayoutInflater.from(parent.context), parent,false)
binding.vm = viewModel
}
}
in XML
<data>
<variable name="vm" type="YourViewModel"/
<variable name="yourItem" type="YourItem"/
</data>
<FrameLayout or something
android:onClick = "#{(view) -> vm.onClickItem(view, yourItem)}">
</FrameLayout>
in ViewModel Class,
fun onClickItem(view : View, yourItem : YourItem){
Log.e(TAG,"$view is clicked with $yourItem")
}
I write about how to listen click or check event from recyclerview items
If you want how to store these datas to DB, you can use your own routine for that in ViewModel Class
Happy coding!