Как удалить строку из recyclerview с помощью room? - PullRequest
6 голосов
/ 05 августа 2020

Я пытаюсь удалить строку из recyclerview, используя room.im, проводя пальцем, чтобы удалить конкретную строку ....

Вот моя таблица адресов ->

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


    }
}

Адаптер:

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 не инициализирована

Действительно нужна помощь ....

Ответы [ 5 ]

2 голосов
/ 19 августа 2020

Следуйте моим простым шагам, чтобы решить вашу проблему,

Шаг 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)

затем обновите co de в методе переопределения

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'

если вы напрямую вызвали метод deleteAddress в классе пользовательского интерфейса, вы получите эту проблему

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

, поэтому используйте такие методы в фоновом потоке,

Если вы действительно хотите выполнить в основном потоке пользовательского интерфейса, сделайте это изменения в вашем коде

в интерфейсе 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 голосов
/ 19 августа 2020

Как было сказано в некоторых других ответах, ваша основная ошибка в том, что вы не вызываете удаление в своей базе данных. Удаление элемента из вашего адаптера не будет эффективным, если ваш набор данных основан на базе данных комнаты, вам необходимо удалить фактическую запись.

Вы не хотите совершать вызовы в свою базу данных из по всему вашему коду, поэтому вы захотите создать интерфейс, который будет действовать как обратный вызов для вашего AddressActivity, который затем сделает за вас вызов удаления. Определите сам интерфейс в адаптере, так как любое действие, использующее этот адаптер, должно ссылаться на интерфейс и реализовывать его.

В AddressAdapter:

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 переместите создание экземпляра адаптера в метод 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 с репозиторием для своих проектов. Хорошо, что у вас уже есть место и живые данные в вашем проекте, но добавленная структура поможет вам упростить внесение изменений в ваши данные и сделать их более понятными. Вы не хотите, чтобы вызовы к своей базе данных были повсюду в своем коде, так что это также поможет уменьшить ваш доступ к базе данных.

ОБНОВЛЕНИЕ: Я заметил, что вы сказали, что вы использовали смахивание для удаления строк, но из вашего кода похоже, что вы действительно используете событие щелчка. Язык моего решения может не соответствовать тому, что вы хотите назвать, но он должен работать одинаково. Затем вместо того, чтобы реализовывать logi c смахивания в вашем адаптере, вы можете установить обработчик смахивания в своей деятельности, где вы инициализируете свой адаптер и представление ресайклера.

В 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) в вашем адаптере или использовать что-то еще метод, который вам нравится для получения правильного адресного объекта от адаптера.

0 голосов
/ 07 августа 2020

В Адресе DAO

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

затем вызовите его из позиции, где выполняется операция смахивания.

Обновление:

Создайте интерфейс, например

interface ListItemClick{
    fun onClick(int id)
}

реализуйте в своем классе активности Address и передайте этот интерфейс адаптеру.

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

затем, если вы реализовали смахивание для удаления, вызовите

listener.onClick(item_id)

в AddressActivity внутри onClick интерфейса ListItemClick

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

Я написал это здесь, поэтому может остаться некоторая орфографическая ошибка. Но, я думаю, у вас есть основная c идея.

0 голосов
/ 17 августа 2020

Проблема в том, что вы удаляете строку только в 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, поэтому я не уверен, что весь мой код правильный, но вы понимаете.

0 голосов
/ 05 августа 2020

Не могли бы вы попробовать это:

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