{"id":1919,"date":"2023-07-12T15:48:07","date_gmt":"2023-07-12T07:48:07","guid":{"rendered":"https:\/\/badgameshow.com\/fly\/?p=1919"},"modified":"2023-07-13T11:04:38","modified_gmt":"2023-07-13T03:04:38","slug":"jetpack-room","status":"publish","type":"post","link":"https:\/\/badgameshow.com\/fly\/jetpack-room\/","title":{"rendered":"\u3010Jetpack\u3011Android Room \u8cc7\u6599\u5eab \u7bc4\u4f8b"},"content":{"rendered":"<h1>\u3010Jetpack\u3011Android Room \u8cc7\u6599\u5eab \u7bc4\u4f8b<\/h1>\n<h4>Android Room \u662f Android \u958b\u767c\u4e2d\u7684\u4e00\u500b\u6301\u4e45\u6027\u5eab\uff0c\u7528\u65bc\u8207 SQLite \u6578\u64da\u5eab\u9032\u884c\u4ea4\u4e92\u3002\u5b83\u662f Google \u5b98\u65b9\u63a8\u85a6\u7684\u4e00\u7a2e\u6578\u64da\u5eab\u89e3\u6c7a\u65b9\u6848\uff0c\u65e8\u5728\u7c21\u5316\u6578\u64da\u5eab\u64cd\u4f5c\u7684\u958b\u767c\u6d41\u7a0b\u3002<\/h4>\n<h4>Room \u63d0\u4f9b\u4e86\u4e00\u500b\u5c0d\u8c61\u95dc\u4fc2\u6620\u5c04\uff08Object-Relational Mapping\uff0cORM\uff09\u7684\u62bd\u8c61\u5c64\uff0c\u5141\u8a31\u958b\u767c\u8005\u4f7f\u7528\u9762\u5411\u5c0d\u8c61\u7684\u65b9\u5f0f\u4f86\u8a2a\u554f\u548c\u64cd\u4f5c\u6578\u64da\u5eab\u3002\u5b83\u5c07Java\u5c0d\u8c61\u6620\u5c04\u5230\u6578\u64da\u5eab\u8868\u683c\uff0c\u4e26\u63d0\u4f9b\u4e86\u65b9\u4fbf\u7684 API \u4f86\u57f7\u884c\u5e38\u898b\u7684 CRUD \u64cd\u4f5c\uff08\u5275\u5efa\u3001\u8b80\u53d6\u3001\u66f4\u65b0\u3001\u522a\u9664\uff09\u3002\u6b64\u5916\uff0cRoom\u9084\u652f\u6301\u6578\u64da\u5eab\u4e8b\u52d9\u3001\u67e5\u8a62\u512a\u5316\u548c\u7570\u6b65\u64cd\u4f5c\u7b49\u529f\u80fd\u3002<\/h4>\n<h4>Room \u7684\u4e3b\u8981\u7d44\u4ef6\u5305\u62ec\u5be6\u9ad4\uff08Entity\uff09\u3001\u6578\u64da\u8a2a\u554f\u5c0d\u8c61\uff08Data Access Object\uff0cDAO\uff09\u548c\u6578\u64da\u5eab\uff08Database\uff09\u3002\u958b\u767c\u8005\u9700\u8981\u5b9a\u7fa9\u5be6\u9ad4\u985e\u4f86\u8868\u793a\u6578\u64da\u5eab\u4e2d\u7684\u8868\u683c\uff0c\u4f7f\u7528 DAO \u985e\u5b9a\u7fa9\u6578\u64da\u5eab\u64cd\u4f5c\u7684\u65b9\u6cd5\uff0c\u4e26\u5275\u5efa\u6578\u64da\u5eab\u985e\u4f86\u5b9a\u7fa9\u6578\u64da\u5eab\u7684\u914d\u7f6e\u548c\u7248\u672c\u7ba1\u7406\u3002<\/h4>\n<h4>\u4f7f\u7528 Room \u53ef\u4ee5\u5927\u5927\u7c21\u5316 Android \u61c9\u7528\u7a0b\u5e8f\u4e2d\u6578\u64da\u5eab\u64cd\u4f5c\u7684\u7de8\u5beb\u5de5\u4f5c\uff0c\u63d0\u9ad8\u4ee3\u78bc\u7684\u53ef\u8b80\u6027\u548c\u7dad\u8b77\u6027\u3002\u5b83\u9084\u63d0\u4f9b\u4e86\u5167\u5efa\u7684\u7de8\u8b6f\u6642\u6aa2\u67e5\u548c\u932f\u8aa4\u63d0\u793a\uff0c\u5e6b\u52a9\u958b\u767c\u8005\u6e1b\u5c11\u5e38\u898b\u7684\u932f\u8aa4\u548c\u63d0\u9ad8\u958b\u767c\u6548\u7387\u3002<\/h4>\n<p><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/developer.android.com\/static\/images\/training\/data-storage\/room_architecture.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/developer.android.com\/static\/images\/training\/data-storage\/room_architecture.png\" alt=\"\" \/><\/a><\/p>\n<hr \/>\n<h4>\u6587\u7ae0\u76ee\u9304<\/h4>\n<ol>\n<li><a href=\"#a\">Room \u5c0e\u5165<\/a><\/li>\n<li><a href=\"#b\">Room \u5275\u5efa Table<\/a><\/li>\n<li><a href=\"#c\">Room \u5275\u5efa Dao<\/a><\/li>\n<li><a href=\"#d\">Room \u5275\u5efa Database<\/a><\/li>\n<li><a href=\"#e\">Room \u7528\u6cd5<\/a><\/li>\n<li><a href=\"#f\">Developer Documents Room<\/a><\/li>\n<\/ol>\n<hr \/>\n<p><a id=\"a\"><\/a><\/p>\n<h4>1.Room \u5c0e\u5165<\/h4>\n<h5>build.gradle(Module :app)<\/h5>\n<pre><code class=\"language-groovy line-numbers\">plugins {\n    id 'kotlin-kapt'\n}\n\ndependencies {\n    \/\/ room\n    implementation 'androidx.room:room-runtime:2.5.2'\n    implementation \"androidx.room:room-ktx:2.5.2\"\n    kapt 'androidx.room:room-compiler:2.5.2'\n\n    \/\/ lifecycle\n    implementation \"androidx.fragment:fragment-ktx:1.6.0\"\n}\n<\/code><\/pre>\n<p><a id=\"b\"><\/a><\/p>\n<h4>2.Room \u5275\u5efa Table<\/h4>\n<h5>NotificationEntity.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">@Entity(tableName = \"notification\")\ndata class NotificationEntity(\n    @PrimaryKey(autoGenerate = true)\n    val id: Int = 0,\n    val sender: String,\n    val body: String,\n    val dateTime: String,\n    val uuid: String,\n    val upload: Boolean\n)\n<\/code><\/pre>\n<p><a id=\"c\"><\/a><\/p>\n<h4>3.Room \u5275\u5efa Dao<\/h4>\n<h5>NotificationDao.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">@Dao\ninterface NotificationDao {\n\n    @Insert\n    suspend fun insert(vararg notifications: NotificationEntity)\n\n    @Query(\"DELETE FROM notification\")\n    suspend fun deleteAll()\n\n    @Query(\"SELECT * FROM notification\")\n    fun getNotifications(): Flow&lt;List&lt;NotificationEntity&gt;&gt;\n\n}\n<\/code><\/pre>\n<p><a id=\"d\"><\/a><\/p>\n<h4>4.Room \u5275\u5efa Database<\/h4>\n<h5>NotificationDatabase.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">@Database(entities = [NotificationEntity::class], version = 1)\nabstract class NotificationDatabase : RoomDatabase() {\n\n    abstract fun notificationDao(): NotificationDAO\n\n    companion object {\n\n        @Volatile\n        private var INSTANCE: NotificationDatabase? = null\n\n        fun getDatabase(context: Context): NotificationDatabase {\n            return INSTANCE ?: synchronized(this) {\n                val instance = Room.databaseBuilder(\n                    context,\n                    NotificationDatabase::class.java,\n                    \"NotificationDatabase\"\n                ).build()\n                INSTANCE = instance\n                instance\n            }\n        }\n\n    }\n}\n<\/code><\/pre>\n<p><a id=\"e\"><\/a><\/p>\n<h4>5.Room \u7528\u6cd5<\/h4>\n<h5>NotificationViewModel.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">class NotificationViewModel(application: Application) : AndroidViewModel(application) {\n\n    private val database = NotificationDatabase.getDatabase(application)\n\n    val notifications = database.notificationDao().getNotifications()\n\n    fun insert(vararg notifications: NotificationEntity) = viewModelScope.launch {\n        database.notificationDao().insert(*notifications)\n    }\n\n    fun deleteAll() = viewModelScope.launch {\n        database.notificationDao().deleteAll()\n    }\n\n}\n\nclass NotificationViewModelFactory(private val application: Application) :\n    ViewModelProvider.Factory {\n    override fun &lt;T : ViewModel&gt; create(modelClass: Class&lt;T&gt;): T {\n        if (modelClass.isAssignableFrom(NotificationViewModel::class.java)) {\n            @Suppress(\"UNCHECKED_CAST\")\n            return NotificationViewModel(application) as T\n        }\n        throw IllegalArgumentException(\"Unknown ViewModel class\")\n    }\n}\n<\/code><\/pre>\n<h5>NotificationListAdapter.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">class NotificationListAdapter: ListAdapter&lt;NotificationEntity, RecyclerView.ViewHolder&gt;(DiffCallback()) {\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {\n        return NotificationViewHolder.create(parent)\n    }\n\n    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n        val notification = getItem(position)\n        when (holder) {\n            is NotificationViewHolder -&gt; {\n                holder.bind(notification.id.toString())\n            }\n        }\n    }\n\n    class NotificationViewHolder(private val binding: RecyclerviewItemBinding) : RecyclerView.ViewHolder(binding.root) {\n\n        fun bind(text: String) {\n            binding.textView.text = text\n        }\n\n        companion object {\n            fun create(parent: ViewGroup): NotificationViewHolder {\n                val binding = RecyclerviewItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)\n                return NotificationViewHolder(binding)\n            }\n        }\n    }\n\n    class DiffCallback : DiffUtil.ItemCallback&lt;NotificationEntity&gt;() {\n        override fun areItemsTheSame(oldItem: NotificationEntity, newItem: NotificationEntity): Boolean {\n            return oldItem == newItem\n        }\n\n        override fun areContentsTheSame(oldItem: NotificationEntity, newItem: NotificationEntity): Boolean {\n            return oldItem == newItem\n        }\n    }\n\n}\n<\/code><\/pre>\n<h5>MainActivity.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">class MainActivity : AppCompatActivity() {\n\n    private lateinit var binding: ActivityMainBinding\n    private val notificationViewModel: NotificationViewModel by viewModels {\n        NotificationViewModelFactory(application)\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        lifecycleScope.launch {\n            notificationViewModel.deleteAll()\n        }\n\n        binding.add.setOnClickListener {\n            lifecycleScope.launch {\n                notificationViewModel.insert(\n                    NotificationEntity(sender = \"Father\", body = \"Hello\", dateTime = Instant.ofEpochMilli(System.currentTimeMillis()).toString(), uuid = \"AA\", upload = false),\n                    NotificationEntity(sender = \"Mother\", body = \"Good Morning\", dateTime = Instant.ofEpochMilli(System.currentTimeMillis()).toString(), uuid = \"BB\", upload = false)\n                )\n\n                withContext(Dispatchers.Main) {\n                    Toast.makeText(this@MainActivity, \"Insert success\", Toast.LENGTH_SHORT).show()\n                }\n            }\n        }\n\n        binding.recyclerview.apply {\n            val notificationAdapter = NotificationListAdapter()\n            adapter = notificationAdapter\n            layoutManager = LinearLayoutManager(this@MainActivity)\n            setHasFixedSize(true)\n\n            lifecycleScope.launch {\n                notificationViewModel.notifications.collect { notifications -&gt;\n                    notificationAdapter.submitList(notifications)\n                }\n            }\n        }\n    }\n}\n<\/code><\/pre>\n<h5>activity_main.xml<\/h5>\n<pre data-language=XML><code class=\"language-markup line-numbers\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\n&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\n    xmlns:app=\"http:\/\/schemas.android.com\/apk\/res-auto\"\n    xmlns:tools=\"http:\/\/schemas.android.com\/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".MainActivity\"&gt;\n\n    &lt;androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id\/recyclerview\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" \/&gt;\n\n    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id\/add\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:src=\"@drawable\/add\"\n        android:contentDescription=\"add\" \/&gt;\n\n&lt;\/androidx.constraintlayout.widget.ConstraintLayout&gt;\n<\/code><\/pre>\n<h5>recyclerview_item.xml<\/h5>\n<pre data-language=XML><code class=\"language-markup line-numbers\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\n&lt;LinearLayout xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"&gt;\n\n    &lt;TextView\n        android:id=\"@+id\/textView\"\n        style=\"@style\/word_title\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@android:color\/holo_orange_light\" \/&gt;\n\n&lt;\/LinearLayout&gt;\n<\/code><\/pre>\n<p><a id=\"f\"><\/a><\/p>\n<h4>6.Developer Documents Room<\/h4>\n<p><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/developer.android.com\/topic\/libraries\/architecture\/viewmodel\" title=\"Open in Documents ViewModel\" target=\"_blank\" rel=\"noopener\">Open in Documents ViewModel<\/a><\/p>\n\n<div style=\"font-size: 0px; height: 0px; line-height: 0px; margin: 0; padding: 0; clear: both;\"><\/div>","protected":false},"excerpt":{"rendered":"<p>\u3010Jetpack\u3011Android Room \u8cc7\u6599\u5eab \u7bc4\u4f8b Android Room \u662f Android \u958b\u767c\u4e2d &hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"pgc_sgb_lightbox_settings":"","footnotes":""},"categories":[106],"tags":[13,107,243],"class_list":["post-1919","post","type-post","status-publish","format-standard","hentry","category-jetpack","tag-android","tag-jetpack","tag-room"],"_links":{"self":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts\/1919","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/comments?post=1919"}],"version-history":[{"count":6,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts\/1919\/revisions"}],"predecessor-version":[{"id":1928,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts\/1919\/revisions\/1928"}],"wp:attachment":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/media?parent=1919"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/categories?post=1919"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/tags?post=1919"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}