今天再來寫一篇關於手機儲存資料之RoomDatabase!

在本網誌中,我也寫了不少關於資料儲存SQlite的相關文章

方便的話可以看一看歐(-‿◦)

碼農日常-『Android studio』SQLite資料庫建立、資料表建立與操作以及Stetho工具

碼農日常-『Android studio』使用Room資料庫以及資料庫監視工具Stetho

碼農日常-『Android studio』Room database Migrate 資料庫版本遷移

 

以上三篇~都是用Java撰寫的

那這次不一樣,我們改使用Kotlin撰寫

 

咦?Kotlin是什麼?我怎麼從來沒聽過

嗯嗯,沒錯,如果不是寫手機程式的人不知道很正常

Kotlin是一種新的程式語言,最早是在2016時發布

他主要是由俄羅斯聖彼得堡的JetBrains開發團隊所發展出來的程式語言,其名稱來自於聖彼得堡附近的科特林島

咦?你問我在哪

我還真的不知道XD

截圖 2021-04-18 上午11.21.46

總之就先Google一下就知道啦

 

好啦扯遠了,本站基於帶新手的目的一直都是用Java做編程

日後等Kotlin更普及後,應該會再寫一些跟Kotlin相關

 

Okay,扯遠了( ・ὢ・ )

那麼,這次的內容就延續之前寫過的文章,做一模一樣的功能出來

看一下範例吧

 

 

以及Github

https://github.com/thumbb13555/RoomDemo_Kotlin

 


 

1. 載入相關Library

 

第一步

..痾要從如何切成Kotlin開始嗎..?

好吧還是講一下好了

想要直接建立為Kotlin環境的話,請在創建專案時在Language選kotlin即可

截圖 2021-04-17 上午11.41.41

 

再來當然是載入Androidx的相關Library囉:D

Kotlin要載入時跟Java有些許的不同,但是一樣是在build.gradle裡面

截圖 2021-04-18 下午12.50.48

然後載入以下粉底白字的部分

 

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.noahliu.roomdemo_kotlin"
        minSdkVersion 23
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'androidx.recyclerview:recyclerview:1.2.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    def room_version = "2.2.6"

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
    androidTestImplementation "androidx.room:room-testing:$room_version"
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.3.1"
    kapt "androidx.lifecycle:lifecycle-compiler:2.3.0-alpha07"
    kapt "androidx.room:room-compiler:2.3.0-alpha02"

    implementation 'com.facebook.stetho:stetho:1.5.1'

}

 

要注意不要漏掉囉~漏掉會無法編譯

 


 

2. 寫介面

 

介面,哎呦小事(´Д`)ヾ

一樣直接給你

 

activity_main.xml

 

截圖 2021-04-18 下午1.04.01

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginTop="32dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button_Modify" />

    <Button
        android:id="@+id/button_Create"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="32dp"
        android:text="新增"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/linearLayout5" />

    <Button
        android:id="@+id/button_Modify"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="8dp"
        android:text="修改"
        app:layout_constraintEnd_toStartOf="@+id/button_Clear"
        app:layout_constraintStart_toEndOf="@+id/button_Create"
        app:layout_constraintTop_toBottomOf="@+id/linearLayout5" />

    <LinearLayout
        android:id="@+id/linearLayout5"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:orientation="vertical"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">



        <LinearLayout
            android:id="@+id/linearLayout2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:orientation="horizontal">

            <TextView

                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="姓名:"
                android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

            <EditText

                android:id="@+id/editText_Name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="3"
                android:ems="10"
                android:inputType="textPersonName" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/linearLayout4"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:orientation="horizontal">

            <TextView

                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="電話:"
                android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

            <EditText

                android:id="@+id/editText_Phone"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="3"
                android:ems="10"
                android:inputType="textPersonName" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/linearLayout3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:orientation="horizontal">

            <TextView

                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="興趣:"
                android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

            <EditText

                android:id="@+id/editText_Hobby"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="3"
                android:ems="10"
                android:inputType="textPersonName" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/linearLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:orientation="horizontal">

            <TextView

                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="其他:"
                android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

            <EditText

                android:id="@+id/editText_else"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="3"
                android:ems="10"
                android:inputType="textPersonName" />
        </LinearLayout>
        <LinearLayout
            android:id="@+id/linearLayout6"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="20dp"
            android:orientation="horizontal">

            <TextView

                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="年齡:"
                android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

            <EditText

                android:id="@+id/editText_age"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="3"
                android:ems="10"
                android:inputType="textPersonName" />
        </LinearLayout>
    </LinearLayout>

    <Button
        android:id="@+id/button_Clear"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="16dp"
        android:text="清空顯示"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/linearLayout5" />

</androidx.constraintlayout.widget.ConstraintLayout>

 


 

3. 建立Room資料庫

 

好了,這裡才是重點!!

來,請先看一下本次資料結構

截圖 2021-04-18 下午1.06.38

 

那麼,我們來從Room套裝組開始

 

3-1 抽象類別DataBase.java

 

首先建立資料庫抽象方法

DataBase.java

@Database(entities = [(MyData::class)],version = 1, exportSchema = false)
abstract class DataBase: RoomDatabase() {
    companion object {
        val DB_NAME = "RecordData.db" //資料庫名稱
        const val TABLENAME = "MyTable"
        @Volatile
        private var instance: DataBase? = null
        @Synchronized
        fun getInstance(context: Context): DataBase? {
            if (instance == null) {
                instance = create(context) //創立新的資料庫
            }
            return instance
        }

        private fun create(context: Context): DataBase{
            return Room.databaseBuilder(context, DataBase::class.java, DB_NAME).build()
        }

    }
    abstract fun getDataUao(): Dao


}

 

有看過這篇

碼農日常-『Android studio』Room database Migrate 資料庫版本遷移

就大概會知道,這邊主導跟資料庫架設有關的事宜

而我通常也會在這邊統一儲存資料庫、資料表名稱等等

提醒一下,這行

@Database(entities = [(MyData::class)],version = 1, exportSchema = false)

 的寫法跟Java有些許不同,特別注意一下(還有記得一定要加...)

 

3-2 實體類MyData.java

 

實體類,這也很單純

直接複製就好

MyData.java

@Entity(tableName = DataBase.TABLENAME)
class MyData{


    @PrimaryKey(autoGenerate = true) //設置是否使ID自動累加
    var id = 0
    var name: String ="未填寫"
    var phone: String = "未填寫"
    var hobby: String = "未填寫"
    var elseInfo: String = "未填寫"
    var age = 18

    constructor(
        name: String,
        phone: String,
        hobby: String,
        elseInfo: String,
        age: Int
    ) {
        this.name = name
        this.phone = phone
        this.hobby = hobby
        this.elseInfo = elseInfo
        this.age = age
    }

    @Ignore //如果要使用多形的建構子,必須加入@Ignore
    constructor(
        id: Int,
        name: String,
        phone: String,
        hobby: String,
        elseInfo: String,
        age: Int
    ) {
        this.id = id
        this.name = name
        this.phone = phone
        this.hobby = hobby
        this.elseInfo = elseInfo
        this.age = age
    }

}

 

3-3 SQL方法介面 Dao.java 

最後是資料庫方法

我這部分特別寫得跟之前文章的內容一樣,除了把語言更換為Kotlin之外

有看過我之前文章的人,可以交叉參考一下喔

 

Dao.java

@Dao
interface Dao {
    /**=======================================================================================*/
    /**======================================================================================= */
    /**簡易新增所有資料的方法 */
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun  //預設萬一執行出錯怎麼辦,REPLACE為覆蓋
            insertData(myData: MyData)

    /**複雜(?)新增所有資料的方法 */
    @Query("INSERT INTO MyTable (name,phone,hobby,elseInfo,age) VALUES(:name,:phone,:hobby,:elseData,:age)")
    fun insertData(
        name: String,
        phone: String,
        hobby: String,
        elseData: String,
        age: Int
    )

    /**=======================================================================================*/
    /**======================================================================================= */
    /**撈取全部資料 */
    @Query("SELECT * FROM MyTable")
    fun displayAll(): MutableList<MyData>

    /**撈取某個名字的相關資料 */
    @Query("SELECT * FROM MyTable WHERE name = :name")
    fun findDataByName(name: String): MutableList<MyData>

    /**=======================================================================================*/
    /**======================================================================================= */
    /**簡易更新資料的方法 */
    @Update
    fun updateData(myData: MyData)

    /**複雜(?)更新資料的方法 */
    @Query("UPDATE MyTable SET name = :name,phone=:phone,hobby=:hobby,elseInfo = :elseInfo,age= :age WHERE id = :id")
    fun updateData(
        id: Int,
        name: String,
        phone: String,
        hobby: String,
        elseInfo: String,
        age: Int
    )

    /**=======================================================================================*/
    /**======================================================================================= */
    /**簡單刪除資料的方法 */
    @Delete
    fun deleteData(myData: MyData)

    /**複雜(?)刪除資料的方法 */
    @Query("DELETE  FROM MyTable WHERE id = :id")
    fun deleteData(id: Int)
}

 

粉底白字的@Dao一定要加XD

我之前還真的會忘記(´・д・`)

 


 

 

4. 建立RecyclerView Adapter

 

好,接下來是RecyclerView的Adapter的部分

這個部分是跟RecyclerView相關的,建議可以看一下我這篇

碼農日常-『Android studio』基本RecyclerView用法

不過..那篇文章已經是我接近兩年前寫的東西了,跟現在的用法會稍稍地不一樣歐(・ωー)~☆

先看一下吧

 

MyAdapter.java

class MyAdapter(val activity:Activity) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

    var arrayList:MutableList<MyData> = ArrayList()
    lateinit var onItemClick:OnItemClickListener

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val tvTitle = itemView.findViewById<TextView>(android.R.id.text1)
    }
    //顯示
    fun showData(){
        Thread{
            arrayList = DataBase.getInstance(activity)!!.getDataUao().displayAll()
            activity.runOnUiThread { notifyDataSetChanged() }
        }.start()
    }
    //新增
    fun insertData(myData: MyData){
        Thread{
            DataBase.getInstance(activity)!!.getDataUao().insertData(myData)
            activity.runOnUiThread {
                showData()
            }
        }.start()
    }
    //刪除
    fun deleteData(position: Int){
        Thread{
            DataBase.getInstance(activity)!!.getDataUao().deleteData(arrayList[position].id)
            activity.runOnUiThread {
                showData()
            }
        }.start()
    }
    //更新
    fun updateData(myData: MyData){
        Thread{
            DataBase.getInstance(activity)!!.getDataUao().updateData(myData)
            activity.runOnUiThread {
                showData()
            }
        }.start()

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(LayoutInflater.from(parent.context)
            .inflate(android.R.layout.simple_list_item_1,null))
    }

    override fun getItemCount(): Int {
        return arrayList.size
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {

        holder.tvTitle.text = arrayList[position].name
        holder.itemView.setOnClickListener{ onItemClick.onItemClick(arrayList[position]) }
    }
    interface OnItemClickListener {
        fun onItemClick(myData: MyData)
    }
}

 

RecyclerView本身的設置我就不重複提了

我都會習慣把一些方法都寫在類別裡,然後再讓Activity去使用

所以分別看到這四個方法

//顯示
fun showData(){
    Thread{
        arrayList = DataBase.getInstance(activity)!!.getDataUao().displayAll()
        activity.runOnUiThread { notifyDataSetChanged() }
    }.start()
}
//新增
fun insertData(myData: MyData){
    Thread{
        DataBase.getInstance(activity)!!.getDataUao().insertData(myData)
        activity.runOnUiThread {
            showData()
        }
    }.start()
}
//刪除
fun deleteData(position: Int){
    Thread{
        DataBase.getInstance(activity)!!.getDataUao().deleteData(arrayList[position].id)
        activity.runOnUiThread {
            showData()
        }
    }.start()
}
//更新
fun updateData(myData: MyData){
    Thread{
        DataBase.getInstance(activity)!!.getDataUao().updateData(myData)
        activity.runOnUiThread {
            showData()
        }
    }.start()

}

 

這邊就是都用上的資料庫的一些操作方法,不過我把模組就寫在這邊

所以等一下Activity在使用的時候,就只要1~2行就可以取用功能囉:D

 


 

5. MainActivity

 

最後就是使用這些功能,主要是MyAdapter的部分..幾乎把功能都寫在裡裡面了(・ω・)b

這邊除了左滑刪除之外,也沒什麼重要的東西了

class MainActivity : AppCompatActivity() {

    var myAdapter:MyAdapter? = null
    var nowSelectedData:MyData? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Stetho.initializeWithDefaults(this);//設置資料庫監視
        initUI()
    }
    private fun initUI(){
        //初始化UI
        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)
        setRecyclerFunction(recyclerView)
        myAdapter = MyAdapter(this)
        recyclerView.adapter = myAdapter
        //顯示目前的資料
        myAdapter!!.showData()
        //設置點擊
        myAdapter!!.onItemClick = object :MyAdapter.OnItemClickListener{
            override fun onItemClick(myData: MyData) {
                nowSelectedData = myData
                editText_Name.setText(nowSelectedData!!.name)
                editText_Phone.setText(nowSelectedData!!.phone)
                editText_Hobby.setText(nowSelectedData!!.hobby)
                editText_else.setText(nowSelectedData!!.elseInfo)
                editText_age.setText(nowSelectedData!!.age.toString())
            }
        }
        //清掉目前顯示
        button_Clear.setOnClickListener {
            nowSelectedData = null
            clear()
        }
        //修改資料
        button_Modify.setOnClickListener {
            if (nowSelectedData == null) return@setOnClickListener
            val data = MyData(
                nowSelectedData!!.id
                ,editText_Name.text.toString()
                ,editText_Phone.text.toString()
                ,editText_Hobby.text.toString()
                ,editText_else.text.toString()
                , try { editText_age.text.toString().toInt() } catch (e: Exception) { 18 }
            )
            myAdapter!!.updateData(data)
            nowSelectedData = null
            clear()
        }
        //新增資料
        button_Create.setOnClickListener {
            val data = MyData(editText_Name.text.toString()
                ,editText_Phone.text.toString()
                ,editText_Hobby.text.toString()
                ,editText_else.text.toString()
                , try { editText_age.text.toString().toInt() } catch (e: Exception) { 18 }
            )
            myAdapter!!.insertData(data)
            clear()
        }


    }
    private fun clear(){
        editText_Name.text.clear()
        editText_Phone.text.clear()
        editText_Hobby.text.clear()
        editText_else.text.clear()
        editText_age.text.clear()
    }



    /**設置RecyclerView的左滑刪除行為 */
    private fun setRecyclerFunction(recyclerView: RecyclerView) {
        val helper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
            //設置RecyclerView手勢功能
            override fun getMovementFlags(
                recyclerView: RecyclerView,
                viewHolder: RecyclerView.ViewHolder
            ): Int {
                return makeMovementFlags(
                    0,
                    ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
                )
            }

            override fun onMove(
                recyclerView: RecyclerView,
                viewHolder: RecyclerView.ViewHolder,
                target: RecyclerView.ViewHolder
            ): Boolean {
                return false
            }

            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                val position = viewHolder.adapterPosition
                when (direction) {
                    ItemTouchHelper.LEFT, ItemTouchHelper.RIGHT -> myAdapter!!.deleteData(position)
                }
            }
        })
        helper.attachToRecyclerView(recyclerView)
    }
}

 

這邊的程式的話,建議就是看一看,老手應該一看就懂,新手可能會有幾個地方找不到我在寫什麼

所以有哪裡看不是很了解部分在留言給我就好囉:D

啊全部看不懂的就..幫我用力拍手就好🤣🤣🤣🤣🤣🤣

那寫到這就可以執行看看,應該就會得到畫面上那個結果囉:D

 


 

這篇寫得好累==

其實這篇的功能跟code我昨天就寫好了

不過要上傳Github時好像出了點問題,最後弄來弄去不小心把寫好的程式蓋掉了

搞到今天週日早上特別起個大早來寫這篇的Code跟內文QQ

今天剛好鄰居又在辦喪事,整個巷子都是敲鑼打鼓的聲音

搞得也沒怎麼睡好...麻

總之,寫完了就是寫完了

感謝各位點閱,最後..

TK

arrow
arrow

    碼農日常 發表在 痞客邦 留言(20) 人氣()