今天再來寫一篇關於手機儲存資料之RoomDatabase!
在本網誌中,我也寫了不少關於資料儲存SQlite的相關文章
方便的話可以看一看歐(-‿◦)
→碼農日常-『Android studio』SQLite資料庫建立、資料表建立與操作以及Stetho工具
→碼農日常-『Android studio』使用Room資料庫以及資料庫監視工具Stetho
→碼農日常-『Android studio』Room database Migrate 資料庫版本遷移
以上三篇~都是用Java撰寫的
那這次不一樣,我們改使用Kotlin撰寫
咦?Kotlin是什麼?我怎麼從來沒聽過
嗯嗯,沒錯,如果不是寫手機程式的人不知道很正常
Kotlin是一種新的程式語言,最早是在2016時發布
他主要是由俄羅斯聖彼得堡的JetBrains開發團隊所發展出來的程式語言,其名稱來自於聖彼得堡附近的科特林島
咦?你問我在哪
我還真的不知道XD
總之就先Google一下就知道啦
好啦扯遠了,本站基於帶新手的目的一直都是用Java做編程
日後等Kotlin更普及後,應該會再寫一些跟Kotlin相關
Okay,扯遠了( ・ὢ・ )
那麼,這次的內容就延續之前寫過的文章,做一模一樣的功能出來
看一下範例吧
以及Github
→https://github.com/thumbb13555/RoomDemo_Kotlin
1. 載入相關Library
第一步
..痾要從如何切成Kotlin開始嗎..?
好吧還是講一下好了
想要直接建立為Kotlin環境的話,請在創建專案時在Language選kotlin即可
再來當然是載入Androidx的相關Library囉:D
Kotlin要載入時跟Java有些許的不同,但是一樣是在build.gradle裡面
然後載入以下粉底白字的部分
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. 寫介面
介面,哎呦小事(´Д`)ヾ
一樣直接給你
<?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資料庫
好了,這裡才是重點!!
來,請先看一下本次資料結構
那麼,我們來從Room套裝組開始
3-1 抽象類別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
實體類,這也很單純
直接複製就好
@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 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用法
不過..那篇文章已經是我接近兩年前寫的東西了,跟現在的用法會稍稍地不一樣歐(・ωー)~☆
先看一下吧
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
今天剛好鄰居又在辦喪事,整個巷子都是敲鑼打鼓的聲音
搞得也沒怎麼睡好...麻
總之,寫完了就是寫完了
感謝各位點閱,最後..
留言列表