How do I remove a row from recyclerview using room?

Viewed 4348

I'm trying to delete an row of recyclerview using room.im doing swipe to delete a particular row....

Here is my Address table-->

@Entity(tableName = "address")
 class Address {
@PrimaryKey(autoGenerate = true)
var id = 0

@ColumnInfo(name = "address")
var address: String? = null
 }

AddressDao:

@Dao
interface AddressDao {
@Insert
suspend fun addData(address: Address)

@Query("select * from address")
fun getAddressesWithChanges() :LiveData<MutableList<Address>>

@Query("SELECT EXISTS (SELECT 1 FROM address WHERE id=:id)")
suspend fun isAddressAdded(id: Int): Int

@Delete
suspend fun delete(address: Address)
  }

Database:

@Database(entities = [Address::class], version = 1)
abstract class Database : RoomDatabase() {
abstract fun AddressDao(): AddressDao
 } 

AddressActivity:

class AddressActivity : AppCompatActivity() {
    private val adapter = AddressAdapter()

    private lateinit var data: LiveData<MutableList<Address>>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.address)

        addbutton.findViewById<View>(R.id.addbutton).setOnClickListener {
            val intent = Intent(this, AddAddressActivity::class.java)
            startActivity(intent)
        }

        val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
        recyclerView.setHasFixedSize(true)
        recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
        recyclerView.adapter = adapter
        recyclerView.addItemDecoration(DividerItemDecorator(resources.getDrawable(R.drawable.divider)))
        recyclerView.addOnScrollListener(object :
            RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                Log.e("RecyclerView", "onScrollStateChanged")
            }

            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
            }
        })


        val application = application as CustomApplication
        data = application.database.AddressDao(). getAddressesWithChanges()
        data.observe(this, Observer { words1 ->
            // Update the cached copy of the words in the adapter.
            words1?.let { adapter.updateData(it) }})


    }
}

Adapter:

class AddressAdapter: RecyclerSwipeAdapter<AddressAdapter.ViewHolder>() {
    private var addresses: MutableList<Address> = Collections.emptyList()
    lateinit var  Database:Database

    override fun onCreateViewHolder(viewGroup: ViewGroup, itemViewType: Int): ViewHolder =
        ViewHolder(LayoutInflater.from(viewGroup.context).inflate(R.layout.address_item, viewGroup, false))

    override fun getSwipeLayoutResourceId(position: Int): Int {
        return R.id.swipe;
    }

    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        val fl: Address = addresses[position]
        viewHolder.tv.setText(fl.address)
        viewHolder.swipelayout.setShowMode(SwipeLayout.ShowMode.PullOut)
        // Drag From Right

        // Drag From Right
        viewHolder.swipelayout.addDrag(
            SwipeLayout.DragEdge.Right,
            viewHolder.swipelayout.findViewById(R.id.bottom_wrapper)
        )
        // Handling different events when swiping
        viewHolder.swipelayout.addSwipeListener(object : SwipeLayout.SwipeListener {
            override fun onClose(layout: SwipeLayout) {
                //when the SurfaceView totally cover the BottomView.
            }

            override fun onUpdate(layout: SwipeLayout, leftOffset: Int, topOffset: Int) {
                //you are swiping.
            }

            override fun onStartOpen(layout: SwipeLayout) {}
            override fun onOpen(layout: SwipeLayout) {
            }

            override fun onStartClose(layout: SwipeLayout) {}
            override fun onHandRelease(
                layout: SwipeLayout,
                xvel: Float,
                yvel: Float
            ) {
            }
        })


        viewHolder.tvDelete.setOnClickListener(View.OnClickListener { view ->
            mItemManger.removeShownLayouts(viewHolder.swipelayout)
            addresses.removeAt(position)

           //What should i do here??????????????????????????
           // val address = Address()

           // Database.AddressDao().delete(address)
            notifyDataSetChanged()
            notifyItemRemoved(position)
            notifyItemRemoved(position)
            notifyItemRangeChanged(position, addresses.size)
            mItemManger.closeAllItems()
            Toast.makeText(
                view.context,
                "Deleted " + viewHolder.tv.getText().toString(),
                Toast.LENGTH_SHORT
            ).show()
        })


        // mItemManger is member in RecyclerSwipeAdapter Class


        // mItemManger is member in RecyclerSwipeAdapter Class
        mItemManger.bindView(viewHolder.itemView, position)
    }


    override fun getItemCount(): Int = addresses.size

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var tv: TextView
        val swipelayout: SwipeLayout
        val tvDelete:TextView



        init {
            tvDelete=itemView.findViewById(R.id.tvDelete)

            tv = itemView.findViewById(R.id.ftv_name)
            swipelayout=itemView.findViewById(R.id.swipe)

        }    }

    fun updateData(addresses:
                   MutableList<Address>) {
        this.addresses = addresses
        notifyDataSetChanged() // TODO: use ListAdapter if animations are needed
    }

}

From the above code I'm able delete a row at a moment but when I revisit the activity it shows that deleted row again

I want to know how can I delete a data which is stored in room using in onBindviewHolder

As per latest answer suggested by @quealegriamasalegre

Here is my CustomApplication:--

class CustomApplication: Application() {
    lateinit var database: Database
        private set
    lateinit var addressDao: AddressDao
        private set

    override fun onCreate() {
        super.onCreate()

        this.database = Room.databaseBuilder<Database>(
            applicationContext,
            Database::class.java, "database"
        ).build()
        addressDao = database.AddressDao()

    }
}

Adapter now:

      viewHolder.tvDelete.setOnClickListener(View.OnClickListener { view ->
        mItemManger.removeShownLayouts(viewHolder.swipelayout)
        addresses.removeAt(position)

        val application = CustomApplication()

        application.database.AddressDao().deleteAddress(position)//here you delete from DB so its gone for good
        //notifyDataSetChanged() dont do this as it will reexecute onbindviewholder and skip a nice animation provided by android
        //notifyItemRemoved(position) execute only once


        notifyDataSetChanged()
        notifyItemRemoved(position)
        notifyItemRemoved(position)
        notifyItemRangeChanged(position, addresses.size)
        mItemManger.closeAllItems()
        Toast.makeText(
            view.context,
            "Deleted " + viewHolder.tv.getText().toString(),
            Toast.LENGTH_SHORT
        ).show()
    })

Now crashing with kotlin.UninitializedPropertyAccessException: lateinit property database has not been initialized

Really need help....

5 Answers

Follow my simple steps to solve your issue,

Step 1: Check once CustomApplication name mentioned or not in AndroidManifest.xml,

<application
    android:name=".CustomApplication"

otherwise you get this issue

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.myapplication/com.example.myapplication.AddressActivity}: java.lang.ClassCastException: android.app.Application cannot be cast to com.example.myapplication.CustomApplication

Step 2: Check your module level build.gradle file

apply this changes

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

check dependencies -- For Kotlin use kapt instead of annotationProcessor

implementation "androidx.room:room-runtime:2.2.5"
kapt "androidx.room:room-compiler:2.2.5"

otherwise you get this issue

java.lang.RuntimeException: cannot find implementation for com.example.myapplication.Database. Database_Impl does not exist

Step 3: check your AddressDao interface, add this function

 @Delete
 suspend fun deleteAddress(address: Address)

Step 4:

in AddressAdapter class, add this listener,

interface ItemListener {
    fun onItemClicked(address: Address, position: Int)
}

add listener variable and setListener function

private lateinit var listener: ItemListener

interface ItemListener {
    fun onItemClicked(address: Address, position: Int)
}

fun setListener(listener: ItemListener) {
    this.listener = listener;
}

then update your code in tvDelete.setOnClickListener method

    viewHolder.tvDelete.setOnClickListener(View.OnClickListener { view ->
        mItemManger.removeShownLayouts(viewHolder.swipelayout)
        addresses.removeAt(position)

        listener.onItemClicked(fl, position)
        
        notifyDataSetChanged()
     //   notifyItemRemoved(position)
     //   notifyItemRangeChanged(position, addresses.size)
        mItemManger.closeAllItems()
        Toast.makeText(
            view.context,
            "Deleted " + viewHolder.tv.getText().toString(),
            Toast.LENGTH_SHORT
        ).show()
    })

Step 5: In AddressActivity class, do this changes

Implement listener here,

class AddressActivity : AppCompatActivity(), AddressAdapter.ItemListener {

then override method

override fun onItemClicked(address: Address, position: Int) {
    
}

then set listener for adapter

        recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
        recyclerView.adapter = adapter
        adapter.setListener(this)

then update code in override method

override fun onItemClicked(address: Address, position: Int) {
    lifecycleScope.launch {
        val application = application as CustomApplication
        application.database.AddressDao().deleteAddress(address)
    }
}

here I used coroutine otherwise you can use AsycTask also

for coroutine add this dependencies in your module build.gradle file

implementation "android.arch.lifecycle:extensions:1.1.1"
kapt "android.arch.lifecycle:compiler:1.1.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'

if you directly called deleteAddress method in UI class, you get this issue

java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

so use such methods in background thread,

If you really want to execute in main UI thread, do this changes in your code

in AddressDao interface,

@Delete
fun deleteAddress(address: Address)

in CustomApplication class, add allowMainThreadQueries()

class CustomApplication : Application() {
    lateinit var database: Database
        private set
    lateinit var addressDao: AddressDao
        private set

    override fun onCreate() {
        super.onCreate()

        this.database = Room.databaseBuilder<Database>(
            applicationContext,
            Database::class.java, "database"
        ).allowMainThreadQueries().build()
        addressDao = database.AddressDao()
    }
}

As some of the other responses have said, your main error is that you are not calling delete on your db. Removing an item from your adapter isn't going to be effective if your data set is based on a room db, you have to delete the actual entry.

You don't want to make calls to your db from all over your code, so you'll want to create an interface that will act as a callback to your AddressActivity, which will then make the delete call for you. Define the interface itself in the adapter, as any activity that makes use of that adapter should reference and implement the interface.

In AddressAdapter:

interface OnItemSwipeListener {
    fun onItemSwipe(address: Address)
}

Then in AdapterActivity, implement the interface and the function

class AddressActivity : AppCompatActivity(), AddressAdapter.OnItemSwipeListener {

    ...

    override fun onItemSwipe(address: Address) {
        application.database.AddressDao().delete(address)
    }

With this code in place, you need to do two things to connect the implementation in the AddressActivity to the AddressAdapter: 1) Pass the AddressActivity to the AddressAdapter and 2) Call the listener on the swipe events.

In AddressActivity, move the instantiation of the Adapter into the onCreate(...) method

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.address)
        
        val adapter = AddressAdapter(this)

Lastly, in AddressAdapter, reconfigure the constructor to take a listener and instead of removing the item from the adapter call the listener

class AddressAdapter(val onItemSwipeListener: OnItemSwipeListener): RecyclerSwipeAdapter<AddressAdapter.ViewHolder>() {
        
        ...
        // addresses.removeAt(position)
        onItemSwipeListener.onItemSwipe(address.get(position))

That should work for you.

Something that would help you is to give your project a better structure. Google recommends the MVVM architecture with a repository for their projects. It's good that you already have room and livedata in your project, but added structure will help you facilitate changes to your data much more effectively and in a way that's easier to understand. You don't want to be making calls to your db from all over in your code, so this will help keep your db access decreased as well.

UPDATE: I noticed you said you were using swipe to delete the rows, but it looks like from your code that you are actually using a click event. The language for my solution may not be consistent for what you want to name things, but it should work all the same.

Something else to consider would be to extend ListAdapter instead of the swipe adapter you are currently using. Then, instead of needing to implement swipe logic in your adapter, you can set a swipe handler in your Activity where you initialize your adapter and recycler view.

In AddressActivity (I know how to do this in Java, you might have to play with the Kotlin syntax, since this looks funny)

val itemTouchHelper = ItemTouchHelper( object : ItemTouchHelper.SimpleCallback(0,
            ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
        override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
            // do nothing
            return false
        }

        override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
            application.database.AddressDao().delete(adapter.getAddressAt(viewHolder.getAdapterPosition())
        }
    }).attachToRecyclerView(mPostRecyclerView)

You will have to create the getAddressAt(pos: Int) function in your adapter, or use whatever method you like for getting the right address object from the adapter.

Could you try this:

viewHolder.tvDelete.setOnClickListener(View.OnClickListener { view ->
    // this code is to have access to the database inside adapter you should find another way to do it,
    // like pass database in contructor for ex...
    val application = application as CustomApplication
    Database = application.database.AddressDao()
   // main code to delete in db
   GlobalScope.launch(Dispatchers.IO) {
    Database.delete(addresses[position])
   }
  //

    mItemManger.removeShownLayouts(viewHolder.swipelayout)
    addresses.removeAt(position)
....

In Address DAO

@Query("DELETE FROM address WHERE id=:id")
fun deleteAddress(id: Int):Int

then call it from the position where your swipe operation is done.

Update:

Create an Interface like

interface ListItemClick{
    fun onClick(int id)
}

implement on your Address activity class and pass this interface to the Adapter.

fun updateData(addresses:MutableList<Address>, listener:ListItemClick) {
        this.addresses = addresses
        this.listener = listener
        notifyDataSetChanged()
}

then, where you implemented swipe to delete, call

listener.onClick(item_id)

on AddressActivity inside onClick of ListItemClick interface

val application = application as CustomApplication
data = application.database.AddressDao().deleteAddress(id)

I wrote it here, so some spelling mistake may remain. But,I think you got the basic idea.

The issue is that you are only deleting the row in Kotlin/Java but not in your SQLite room DB. I guess you should be able to solve your problem by simply adding a line that deletes an address in your DB. here an example of how i would do this for your tvDelete clickable view:

val application = application as CustomApplication
    

viewHolder.tvDelete.setOnClickListener(View.OnClickListener { view ->
        mItemManger.removeShownLayouts(viewHolder.swipelayout)
        addresses.removeAt(position)

        //remember that in onBind you already saved the current Address as "fl"

        application.database.AddressDao().delete(fl)//here you delete from DB so its gone for good
        //notifyDataSetChanged() dont do this as it will reexecute onbindviewholder and skip a nice animation provided by android
        notifyItemRemoved(position)
        //notifyItemRemoved(position) execute only once
        notifyItemRangeChanged(position, addresses.size)//i dont think you will need this
        mItemManger.closeAllItems()
        Toast.makeText(
            view.context,
            "Deleted " + viewHolder.tv.getText().toString(),
            Toast.LENGTH_SHORT
        ).show()
    })

so i guess you were quite close already. please mind that i code in java so im not super confident all my code is correct but you get the idea.

Related