【问题标题】:How do I remove a row from recyclerview using room?如何使用房间从 recyclerview 中删除一行?
【发布时间】:2020-08-05 13:52:49
【问题描述】:

我正在尝试使用 room.im 删除一行 recyclerview 进行滑动以删除特定行....

这是我的地址表-->

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

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

地址道:

@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(entities = [Address::class], version = 1)
abstract class Database : RoomDatabase() {
abstract fun AddressDao(): AddressDao
 } 

地址活动:

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) }})


    }
}

适配器:

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
    }

}

从上面的代码中,我可以一次删除一行,但是当我重新访问该活动时,它会再次显示已删除的行

我想知道如何使用onBindviewHolder删除存储在房间中的数据

根据@quealegriamasalegre 建议的最新答案

这是我的自定义应用程序:--

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()

    }
}

现在适配器:

      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()
    })

现在因 kotlin.UninitializedPropertyAccessException 崩溃:lateinit 属性数据库尚未初始化

真的需要帮助....

【问题讨论】:

  • 您不必这样做:val application = CustomApplication()(它是由 Android 初始化的核心实例)。将您的 applicationContext 转换为 CustomApplication 并访问数据库。
  • 我应该怎么做 --> Cast your applicationContext to CustomApplication @MaximFirsoff 在适配器中?
  • @MaximFirsoff 如果我这样做 -->val application = context as CustomApplication 我得到错误--> java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
  • 是的,没错,数据库访问应该在后台线程中使用。

标签: android android-recyclerview android-room android-livedata delete-row


【解决方案1】:

按照我的简单步骤来解决您的问题,

第 1 步: 检查一次CustomApplication 名称是否在AndroidManifest.xml 中提及,

<application
    android:name=".CustomApplication"

否则你会遇到这个问题

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

第 2 步: 检查你的模块级别build.gradle文件

应用此更改

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

检查依赖关系——对于 Kotlin,使用 kapt 而不是 annotationProcessor

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

否则你会遇到这个问题

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

第 3 步: 检查你的AddressDao 接口,添加这个功能

 @Delete
 suspend fun deleteAddress(address: Address)

第 4 步:

AddressAdapter类中,添加这个监听器,

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

添加监听变量和setListener函数

private lateinit var listener: ItemListener

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

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

然后在 tvDelete.setOnClickListener 方法中更新您的代码

    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()
    })

第 5 步:AddressActivity 类中,进行此更改

在此处实现监听器,

class AddressActivity : AppCompatActivity(), AddressAdapter.ItemListener {

然后覆盖方法

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

然后为适配器设置监听器

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

然后在 override 方法中更新代码

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

这里我使用协程,否则你也可以使用 AsycTask

对于协程,在你的模块build.gradle文件中添加这个依赖项

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'

如果你在 UI 类中直接调用 deleteAddress 方法,就会出现这个问题

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

所以在后台线程中使用这样的方法,

如果您真的想在主 UI 线程中执行,请在您的代码中进行此更改

AddressDao界面中,

@Delete
fun deleteAddress(address: Address)

CustomApplication类中,添加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()
    }
}

【讨论】:

【解决方案2】:

正如其他一些回复所说,您的主要错误是您没有在您的数据库上调用 delete。如果您的数据集基于房间数据库,则从适配器中删除项目不会有效,您必须删除实际条目。

您不想从整个代码中调用您的数据库,因此您需要创建一个接口,作为对您的 AddressActivity 的回调,然后它会为您进行删除调用。在适配器中定义接口本身,因为任何使用该适配器的活动都应该引用并实现该接口。

在地址适配器中:

interface OnItemSwipeListener {
    fun onItemSwipe(address: Address)
}

然后在AdapterActivity中,实现接口和功能

class AddressActivity : AppCompatActivity(), AddressAdapter.OnItemSwipeListener {

    ...

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

有了这段代码,您需要做两件事来将 AddressActivity 中的实现连接到 AddressAdapter:1) 将 AddressActivity 传递给 AddressAdapter,2) 在滑动事件上调用监听器。

在AddressActivity中,将Adapter的实例化移动到onCreate(...)方法中

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

最后,在 AddressAdapter 中,重新配置构造函数以获取侦听器,而不是从适配器中移除项目调用侦听器

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

这应该适合你。

对您有帮助的是为您的项目提供更好的结构。 Google 为他们的项目推荐了带有存储库的 MVVM 架构。项目中已经有了空间和实时数据,这很好,但是添加的结构将帮助您更有效地以更容易理解的方式促进对数据的更改。您不想在代码中从各处调用您的数据库,因此这也有助于减少您的数据库访问。

更新: 我注意到您说您使用滑动来删除行,但是从您的代码中看起来您实际上正在使用单击事件。我的解决方案的语言可能与您想要命名的内容不一致,但它应该可以正常工作。

要考虑的其他事情是扩展 ListAdapter 而不是您当前使用的滑动适配器。然后,无需在适配器中实现滑动逻辑,您可以在 Activity 中设置滑动处理程序,在其中初始化适配器和回收器视图。

在 AddressActivity 中(我知道如何在 Java 中执行此操作,您可能必须使用 Kotlin 语法,因为这看起来很有趣)

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)

您必须在适配器中创建 getAddressAt(pos: Int) 函数,或使用您喜欢的任何方法从适配器获取正确的地址对象。

【讨论】:

【解决方案3】:

你可以试试这个:

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)
....

【讨论】:

    【解决方案4】:

    在地址 DAO

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

    然后从完成滑动操作的位置调用它。

    更新:

    创建一个类似的接口

    interface ListItemClick{
        fun onClick(int id)
    }
    

    实现您的地址活动类并将此接口传递给适配器。

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

    然后,在您实现滑动删除的地方,调用

    listener.onClick(item_id)
    

    ListItemClick接口的onClick里面的AddressActivity

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

    我在这里写的,所以可能会有一些拼写错误。但是,我认为您已经掌握了基本概念。

    【讨论】:

    • 你能告诉我应该如何在tvdelete下的适配器下call it from the position where your swipe operation is done....我不明白我应该怎么称呼它?
    • 这一行出错 --> data = application.database.AddressDao().deleteAddress(id) 表示需要的是 livedata> 找到的是 Int
    • 我还在上面发布了更新的适配器代码..请参阅....并且从未使用过 updateData1 ...需要帮助...
    • 我在 deleteAddress DAO 方法上犯了一个愚蠢的错误,你现在可以检查更新吗?而在 updateData 和 updateData1 方法中,你应该删除 updateData 方法。请让我知道它是否有效。
    • 如果我在updateData 中进行更改,比如添加监听器 --> 它会影响我在房间中的add(这里--> data = application.database.AddressDao().getAddressesWithChanges()) ...这就是为什么我使用updateData1(单独的方法)
    【解决方案5】:

    问题是您只删除了 Kotlin/Java 中的行,而不是 SQLite 房间数据库中的行。我想您应该能够通过简单地添加一行来删除数据库中的地址来解决您的问题。这是我如何为您的 tvDelete 可点击视图执行此操作的示例:

    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()
        })
    

    所以我猜你已经很接近了。请注意,我是用 Java 编写代码的,所以我对我的所有代码都不是非常有信心,但你明白了。

    【讨论】:

    • 错误(红色)AddressDao() 代码 --> Database.AddressDao().delete(address) \
    • 看看我的编辑。正如我所说,在 kotlin 上不太好,但如果你阅读了答案,你应该找到正确的路径
    • 我说的是我无法访问数据库后的AddressDao
    • 我刚刚添加了一个编辑,我认为这是 kotlin 实例化房间数据库的正确语法。抱歉,我只是在使用你已经拥有的代码,我没想到要检查它
    • crash with --> kotlin.UninitializedPropertyAccessException: lateinit property database has not been initialized 你可以看到我的代码已编辑
    猜你喜欢
    • 2018-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多