Android Kotlin-使用一对多实体关系时出错

Android Kotlin-使用一对多实体关系时出错,android,database,sqlite,kotlin,android-room,Android,Database,Sqlite,Kotlin,Android Room,当我最初构建我的应用程序时,它采用了cardName并将其托管在一个RecyclerView中,该视图按名称保存了每张卡的列表。因为我还是开发新手,所以我这样做是为了看看我是否可以让RecyclerView只处理名称。现在,我希望能够在一对多的关系中为每张卡添加类别,我按照下面的说明进行了操作,但我可能遗漏了一些内容,因为我遇到了下面的错误。理想情况下,应用程序应该和以前一样工作,只显示名称,但现在也有额外的值,随着时间的推移,我可以慢慢添加到片段中。我非常感谢任何帮助,因为我可能忽略了一些事情

当我最初构建我的应用程序时,它采用了cardName并将其托管在一个RecyclerView中,该视图按名称保存了每张卡的列表。因为我还是开发新手,所以我这样做是为了看看我是否可以让RecyclerView只处理名称。现在,我希望能够在一对多的关系中为每张卡添加类别,我按照下面的说明进行了操作,但我可能遗漏了一些内容,因为我遇到了下面的错误。理想情况下,应用程序应该和以前一样工作,只显示名称,但现在也有额外的值,随着时间的推移,我可以慢慢添加到片段中。我非常感谢任何帮助,因为我可能忽略了一些事情。提前谢谢

卡片

建造

正在执行项目C:\Users\JJLog\AndroidStudio项目\PointMax中的任务:[:app:assembleDebug]

任务:应用程序:预构建最新 任务:app:preDebugBuild-update 任务:应用程序:CompiledBugaidl无源代码 任务:app:generateDebugBuildConfig最新 任务:应用程序:WritedBugApplicationID最新 任务:应用程序:CompiledBugRenderScript无源代码 任务:应用程序:生成最新的FeargsDebug 任务:app:prepareLintJar最新 任务:应用程序:准备发布最新版本 任务:应用程序:生成最新的BugSources 任务:应用程序:dataBindingExportBuildInfoDebug最新 任务:应用程序:数据绑定合并依赖项工件缺陷最新 任务:应用程序:DataBindingMergenclasseDbug最新 任务:应用程序:生成最新的BugResValues 任务:应用程序:生成最新的BugResources 任务:应用程序:合并调试资源最新 任务:应用程序:DataBindingGenBaseClasseDebug最新 任务:应用程序:DataBindingExportFeaturePackageIDSDDebug最新 任务:应用程序:mainApkListPersistenceDebug最新 任务:应用程序:CreateDebuggCompatibleScreenManifests最新 任务:应用程序:extractDeepLinksDebug最新 任务:应用程序:processDebugManifest最新 任务:应用程序:processDebugResources最新 任务:应用程序:kaptGenerateStubsDebugKotlin 任务:应用程序:合并调试着色器最新 任务:应用程序:最新的CompiledBugShader 任务:应用程序:生成最新的BugAssets 任务:应用程序:合并调试资产最新 任务:app:processDebugJavaRes NO-SOURCE 任务:应用程序:CheckDebugDuplicateClass最新 任务:应用程序:DesugardBugFileDependencies最新 任务:应用程序:mergeExtDexDebug最新 任务:app:mergeDebugJniLibFolders最新 任务:应用程序:mergeDebugNativeLibs最新 任务:应用程序:条带调试符号最新 任务:应用程序:验证设计调试最新

任务:应用程序:kaptDebugKotlin 用于代码生成的ANTLR工具版本4.5.3与用于解析器编译的当前运行时版本4.7.1ANTLR运行时版本4.5.3不匹配用于代码生成的当前运行时版本4.7.1ANTLR工具版本4.5.3与用于解析器的当前运行时版本4.7.1ANTLR运行时版本4.5.3不匹配编译与当前运行时版本4.7.1C不匹配:\Users\jjlog\AndroidStudioProjects\PointMax\app\build\tmp\kapt3\stubs\debug\com\example\android\PointMax\database\CreditCards.java:12:错误:查询有问题:[SQLITE\u error]SQL错误或缺少数据库没有这样的表:Category private final java.util.List categories=null; ^C:\Users\jjlog\AndroidStudioProjects\PointMax\app\build\tmp\kapt3\stubs\debug\com\example\android\PointMax\database\CardDao.java:17:错误:参数类型必须是用@Entity注释的类或其集合/数组。 com.example.android.pointmax.database.CreditCards信用卡,@org.jetbrains.annotations.NotNull

任务:应用程序:kaptDebugKotlin失败 ^ 失败:生成失败,出现异常

*出了什么问题: 任务“:app:kaptDebugKotlin”的执行失败

执行org.jetbrains.kotlin.gradle.internal.KaptExecution时出错 java.lang.reflect.InvocationTargetException无错误消息

尝试: 使用-stacktrace选项运行以获取堆栈跟踪。使用-info或-debug选项运行以获得更多日志输出。使用-scan运行以获得完整的洞察力。 得到更多的帮助 生成在11秒内失败 29项可执行任务:2项已执行,27项最新

编辑: 绑定适配器

卡适配器

回收视图项目

fragment_wallet.xml

WalletViewModel

关于应用程序中的一对多关系,我仍然面临错误:

任务:应用程序:kaptDebugKotlin 用于代码生成的ANTLR工具版本4.5.3与用于解析器编译的当前运行时版本4.7.1ANTLR运行时版本4.5.3不匹配用于代码生成的当前运行时版本4.7.1ANTLR工具版本4.5.3与用于解析器的当前运行时版本4.7.1ANTLR运行时版本4.5.3不匹配编译与当前运行时版本4.7.1C不匹配: \Users\jjlog\AndroidStudioProjects\PointMax\app\build\tmp\kapt3\stubs\debug\com\example\android\PointMax\database\CreditCards.java:12:错误:查询有问题:[SQLITE\u error]SQL错误或缺少数据库没有这样的表:Category private final java.util.List categories=null; ^C:\Users\jjrog\AndroidStudioProjects\PointMax\app\build\tmp\kapt3\stubs\debug\com\example\android\PointMax\database\CardDao.java:22:错误:com.example.android.PointMax.database.CardDao是com.example.android.PointMax.database.CardRoomDatabase的一部分,但该实体不在数据库中。也许您忘记将com.example.android.pointmax.database.Category添加到@database的entities部分了? 公共抽象java.lang.ObjectinsertCategory@org.jetbrains.annotations.NotNull [警告]已请求增量注释处理,但由于以下处理器不是增量的,因此禁用了支持:androidx.room.RoomProcessor DYNAMIC

任务:应用程序:kaptDebugKotlin失败 ^ 失败:生成失败,出现异常

出了什么问题: 任务“:app:kaptDebugKotlin”的执行失败。 执行org.jetbrains.kotlin.gradle.internal.KaptExecution时出错 java.lang.reflect.InvocationTargetException无错误消息

堆栈跟踪:

原因:org.gradle.workers.internal.DefaultWorkerExecutor$WorkExecutionException:执行org.jetbrains.kotlin.gradle.internal.KaptExecution时发生故障

原因:java.lang.reflect.InvocationTargetException


由以下原因引起:org.jetbrains.kotlin.kapt3.base.util.kaptbase错误:注释处理时出错

我认为,问题出在您的DAO中:

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(card: CreditCards)
信用卡-只是一个数据类,而不是房间的实体。没有与之关联的SQLite表。因此,您不能使用它来插入。假设这个类对于房间是只读的,它在查询期间只动态地填充数据,您只能在查询中作为结果的类型使用它


您还有另外两个实体-卡片和类别,它们中的每一个都与SQLite表相关联。您应该为它们实现@Insert方法。在其中一个或两个表中对这些表进行更改后,您将通过查询得到新的结果,结果为CreditCards值

运行./gradlew clean build-stacktrace并在此处发布相关错误。更新以添加stacktrace错误。感谢您澄清Android如何获取实体值以及我如何无法通过插入将值输入数据类。但是,我仍然看到上面与categories=null相关的错误。是否有一个原因类别被设置为空,即使我以与卡片中相同的方式将值插入其中?再次感谢你的帮助;非常感谢。房间的堆栈跟踪非常有用,您所需要的就是:。。。可能您忘记将com.example.android.pointmax.database.Category添加到@database?..的实体部分:非常感谢你的帮助。因为我添加了一个新实体,所以我需要更新CardRoomDatabase的entities部分,使其具有arrayOfCard::class、Category::class。现在,我知道只在数据库中查找实体的插入,并在@database中声明新实体。谢谢你的帮助!
package com.example.android.pointmax.database

import androidx.lifecycle.LiveData
import androidx.room.*


@Dao
interface CardDao {
    @Transaction
    @Query("SELECT * from Card")
    fun getCards(): LiveData<List<CreditCards>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(card: Card)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertCategory(category: Category)

    @Query("DELETE FROM Card")
    suspend fun deleteAll()

    @Query("DELETE FROM Card WHERE cardName = :name")
    suspend fun deleteByName(name: String)

    @Query("UPDATE Card SET cardName = :newName WHERE cardName = :oldName")
    suspend fun editName(newName: String, oldName: String)
}
package com.example.android.pointmax.database

import androidx.lifecycle.LiveData

// Declares the DAO as a private property in the constructor. Pass in the DAO
// instead of the whole database, because you only need access to the DAO
class CardRepository(private val cardDao: CardDao) {

    // Room executes all queries on a separate thread.
    // Observed LiveData will notify the observer when the data has changed.
    val allCards: LiveData<List<CreditCards>> = cardDao.getCards()

    suspend fun insert(card: Card) {
        cardDao.insert(card)
    }

    suspend fun insertCategory(category: Category) {
        cardDao.insertCategory(category)
    }

    suspend fun deleteByName(name: String){
        cardDao.deleteByName(name)
    }

    suspend fun editName(newName: String, oldName: String){
        cardDao.editName(newName, oldName)
    }
}
package com.example.android.pointmax.database

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch


// Annotates class to be a Room Database with a table (entity) of the Card class
@Database(entities = arrayOf(Card::class), version = 1, exportSchema = false)
public abstract class CardRoomDatabase : RoomDatabase() {

    abstract fun cardDao(): CardDao

    companion object {
        // Singleton prevents multiple instances of database opening at the
        // same time.
        @Volatile
        private var INSTANCE: CardRoomDatabase? = null

        fun getDatabase(
            context: Context,
            scope: CoroutineScope
        ): CardRoomDatabase {
            val tempInstance = INSTANCE
            if (tempInstance != null) {
                return tempInstance
            }
            synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    CardRoomDatabase::class.java,
                    "card_database"
                ).addCallback(CardDatabaseCallback(scope)).build()
                INSTANCE = instance
                return instance
            }
        }

        private class CardDatabaseCallback(
            private val scope: CoroutineScope
        ) : RoomDatabase.Callback() {

            override fun onOpen(db: SupportSQLiteDatabase) {
                super.onOpen(db)
                INSTANCE?.let { database ->
                    scope.launch {
                        populateDatabase(database.cardDao())
                    }
                }
            }

            suspend fun populateDatabase(cardDao: CardDao) {
                // Delete all content here.
                cardDao.deleteAll()

                // Add sample cards.
                var card = Card(cardName = "Petal Credit Card")
                cardDao.insert(card)
                var category = Category(cardCategoryId = card.cardId, type = "General", earnRate = 1.5, protection = 0, redeemValue = "cash")
                cardDao.insertCategory(category)
//                card = Card(cardName = "Discover IT")
//                cardDao.insert(card)
            }
        }
    }
}
package com.example.android.pointmax

import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.android.pointmax.database.Card
import com.example.android.pointmax.database.CreditCards


/**
 * When there are no Cards (data is null), hide the [RecyclerView], otherwise show it.
 */
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, data: List<CreditCards>?) {
    val adapter = recyclerView.adapter as CardAdapter
    adapter.submitList(data)
}
package com.example.android.pointmax

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.android.pointmax.database.Card
import com.example.android.pointmax.database.CreditCards
import com.example.android.pointmax.databinding.RecyclerviewItemBinding
import com.example.android.pointmax.ui.wallet.WalletViewModel
import kotlinx.android.synthetic.main.recyclerview_item.view.*


class CardAdapter(val onClickListener: OnClickListener) :
    ListAdapter<CreditCards, CardAdapter.CardViewHolder>(DiffCallback) {

    /**
     * The CardViewHolder constructor takes the binding variable from the associated
     * LayoutViewItem, which nicely gives it access to the full [Card] information.
     */
    class CardViewHolder (private var binding: RecyclerviewItemBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind (cards: CreditCards){
            binding.creditCard = cards
            // This is important, because it forces the data binding to execute immediately,
            // which allows the RecyclerView to make the correct view size measurements
            binding.executePendingBindings()
        }
    }

    /**
     * Allows the RecyclerView to determine which items have changed when the [List] of [Card]
     * has been updated.
     */
    companion object DiffCallback : DiffUtil.ItemCallback<CreditCards>() {
        override fun areItemsTheSame(oldItem: CreditCards, newItem: CreditCards): Boolean {
            return oldItem.card === newItem.card
        }

        override fun areContentsTheSame(oldItem: CreditCards, newItem: CreditCards): Boolean {
            return oldItem.card.cardName == newItem.card.cardName
        }
    }

    /**
     * Create new [RecyclerView] item views (invoked by the layout manager)
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
        return CardViewHolder(RecyclerviewItemBinding.inflate(LayoutInflater.from(parent.context)))
    }

    /**
     * Replaces the contents of a view (invoked by the layout manager)
     */
    override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
        val current = getItem(position)
        holder.itemView.setOnClickListener {
            onClickListener.onClick(current)
        }
        holder.bind(current)
    }


    /**
     * Custom listener that handles clicks on [RecyclerView] items.  Passes the [CreditCards]
     * associated with the current item to the [onClick] function.
     * @param clickListener lambda that will be called with the current [Card]
     */
    class OnClickListener(val clickListener: (cards : CreditCards) -> Unit) {
        fun onClick(cards:CreditCards) = clickListener(cards)
    }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="creditCard"
            type="com.example.android.pointmax.database.CreditCards" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <androidx.constraintlayout.widget.ConstraintLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TextView
                android:id="@+id/card_name"
                style="@style/card_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@{creditCard.card.cardName.toString()}"
                android:background="@android:color/holo_orange_light"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                android:layout_marginBottom="@dimen/activity_vertical_margin"
                />
        </androidx.constraintlayout.widget.ConstraintLayout>
    </LinearLayout>
</layout>
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="viewModel"
            type="com.example.android.pointmax.ui.wallet.WalletViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.android.pointmax.MainActivity">

        <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/wallet_recyclerview"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                tools:listitem="@layout/recyclerview_item"
                app:layout_constraintBottom_toTopOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="parent"
                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                app:listData="@{viewModel.allCards}"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
package com.example.android.pointmax.ui.wallet

import android.app.Application
import androidx.lifecycle.*
import com.example.android.pointmax.database.Card
import com.example.android.pointmax.database.CardRepository
import com.example.android.pointmax.database.CardRoomDatabase
import com.example.android.pointmax.database.CreditCards
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job

class WalletViewModel(application: Application) : AndroidViewModel(application) {
    private val repository: CardRepository
    // Using LiveData and caching what getAlphabetizedWords returns has several benefits:
    // - We can put an observer on the data (instead of polling for changes) and only update the
    //   the UI when the data actually changes.
    // - Repository is completely separated from the UI through the ViewModel.

    // The external LiveData interface to the property is immutable, so only this class can modify
    val allCards: LiveData<List<CreditCards>>

    // Internally, we use a MutableLiveData to handle navigation to the selected cxa
    private val _navigateToSelectedCard = MutableLiveData<Card>()

    // The external immutable LiveData for the navigation property
    val navigateToSelectedCard: LiveData<Card>
        get() = _navigateToSelectedCard

    // Create a Coroutine scope using a job to be able to cancel when needed
    private var viewModelJob = Job()

    // the Coroutine runs using the Main (UI) dispatcher
    private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main)

    /**
     * When the card is clicked, set the [_navigateToSelectedCard] [MutableLiveData]
     * @param card The [Card] that was clicked on.
     */
    fun displayCardDetails(card: Card) {
        _navigateToSelectedCard.value = card
    }

    /**
     * After the navigation has taken place, make sure navigateToSelectedProperty is set to null
     */
    fun displayCardDetailsComplete() {
        _navigateToSelectedCard.value = null
    }

    init {
        val cardsDao = CardRoomDatabase.getDatabase(application, viewModelScope).cardDao()
        repository = CardRepository(cardsDao)
        allCards = repository.allCards
    }
}
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(card: CreditCards)