๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“ฑ| Android/๐Ÿš€ | Jetpack

[Android] Room ์‚ฌ์šฉ๋ฒ•๊ณผ RecyclerView๋ฅผ ํ™œ์šฉํ•œ ์˜ˆ์ œ

by immgga 2022. 4. 12.

์ด๋ฒˆ ์˜ˆ์ œ๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ์˜ ๊ฐ€์ƒ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์ธ room์„ ํ™œ์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ , ๊ทธ๊ฒƒ์„ recyclerview์— ๋ณด์ด๊ฒŒ ํ•˜๋Š” ์˜ˆ์ œ๋ฅผ ํ•ด๋ณด๊ฒ ๋‹ค.

 

room ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋จผ์ € gradle์— ์ถ”๊ฐ€๋ฅผ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

 

build.gradle(app)

plugins {
    id 'kotlin-kapt'
    ...
}

android {
    ...
}

dependencies {
    implementation "androidx.room:room-runtime:2.4.2"
    kapt "androidx.room:room-compiler:2.4.2"
}

์ถ”๊ฐ€๋ฅผ ํ•˜๊ณ  ๋‚˜์„œ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋“ค์–ด๊ฐˆ ๋ฐ์ดํ„ฐ๋“ค์„ ๋งŒ๋“ค์ค˜์•ผ ํ•œ๋‹ค.

1. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐœ์ฒด ํด๋ž˜์Šค ์ƒ์„ฑ

@Entity(tableName = "chat_table")
class ChatEntity(
    @PrimaryKey(autoGenerate = true) var uid: Long,
    @ColumnInfo(name = "sendId") var sendId: String,        // ๋ณด๋‚ธ ์‚ฌ๋žŒ์˜ id
    @ColumnInfo(name = "comment") var comment: String,      // ๋ณด๋‚ธ ๊ธ€
    @ColumnInfo(name = "sendTime") var sendTime: String     // ๋ณด๋‚ธ ์‹œ๊ฐ„
) {
    constructor(): this(0, "", "", "00:00")
}

 

  • class์— @Entity ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ ๊ฐœ์ฒด ํด๋ž˜์Šค๋ผ๋Š” ๊ฒƒ์„ ์•Œ๋ ค์ค€๋‹ค.
  • @PrimaryKey๋Š” ๊ธฐ๋ณธ ํ‚ค ๊ฐ™์€ ๊ฐœ๋…์œผ๋กœ, ๋ฌด์กฐ๊ฑด ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  • autoGenerate๋Š” ๊ธฐ๋ณธํ‚ค ์ž๋™ ์ƒ์„ฑ ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.(true๋Š” ์ž๋™ ์ƒ์„ฑ)
  • @ColumnInfo ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋“ค์–ด๊ฐˆ ์•„์ดํ…œ๋“ค์„ ์ง€์ •ํ•ด์ค€๋‹ค.
  • constructor ๋ถ€๋ถ„์€ default ๊ฐ’์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.

2. Data Access Object(DAO) ๋งŒ๋“ค๊ธฐ

DAO๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์ฟผ๋ฆฌ๊ฐ€ ์žˆ๋Š” ๊ณณ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋“ค์„ ์ •์˜ํ•ด ๋†“๋Š” ๊ณณ์ด๋‹ค.(๊ธฐ๋ณธ์ ์ธ sql ์ง€์‹์ด ํ•„์š”ํ•˜๋‹ค)

@Dao
interface UserDao {
    // ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ
    @Query("SELECT * FROM user_table")
    fun getAll(): List<UserEntity>

    // ์‚ฝ์ž… ์ฟผ๋ฆฌ
    @Insert(onConflict = REPLACE)
    fun insert(userEntity: UserEntity)

    // ํŠน์ • ๋ฐ์ดํ„ฐ ์‚ญ์ œ ์ฟผ๋ฆฌ
    @Query("DELETE from user_table WHERE id = :id")
    fun deleteAll(id: String)
}

 

  • ๋จผ์ € interface์— @Dao ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ Dao ํด๋ž˜์Šค๋ผ๋Š” ๊ฒƒ์„ ์•Œ๋ฆฐ๋‹ค.
  • @Query, @Insert ์–ด๋…ธํ…Œ์ด์…˜์•ˆ์— sql ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.
  • onConflict ๋ถ€๋ถ„์€ ์ค‘๋ณต๋œ ๊ธฐ๋ณธ ํ‚ค ๊ฐ’์ด ์กด์žฌํ•  ๊ฒฝ์šฐ ์ตœ๊ทผ์˜ ๊ฒƒ์œผ๋กœ ๊ต์ฒดํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ํ…Œ์ด๋ธ” ํ•˜๋‚˜๊ฐ€ ์žˆ๋‹ค๊ณ  ์น˜์ž.

id(๊ธฐ๋ณธ ํ‚ค) name pw
user1 ditntlro 1234
user2 rotntldl 4321

 

id๊ฐ€ ๊ธฐ๋ณธ ํ‚ค์ผ๋•Œ, id๊ฐ€ user1์ธ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์™”๋‹ค.

user1 orltntid 1111

 

๊ธฐ๋ณธ ํ‚ค์ธ id๊ฐ€ ์ค‘๋ณต๋˜๋ฏ€๋กœ ๊ธฐ์กด์˜ ๋ฐ์ดํ„ฐ์ธ ditntlro๊ฐ€ ์ œ๊ฑฐ๋˜๊ณ , ์ƒˆ๋กœ ๋“ค์–ด์˜จ orltntid๊ฐ€ ๋“ค์–ด์˜จ๋‹ค.

 

๊ฒฐ๊ณผ ํ…Œ์ด๋ธ”

id name pw
user1 orltntid 1111
user2 rotntldl 4321

3. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋นŒ๋” ๋งŒ๋“ค๊ธฐ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋นŒ๋”๋Š” ์‚ฌ์šฉ์ž๊ฐ€ room์„ ์ˆ˜ํ–‰์‹œํ‚ค๋ ค๋ฉด ์žˆ์–ด์•ผ ํ•˜๋Š” ๊ณณ์ด๋‹ค.

@Database(entities = [ChatEntity::class], version = 1)
abstract class ChatDbBuilder: RoomDatabase() {
    abstract fun chatDao(): ChatDao

    companion object {
        private var INSTANCE: ChatDbBuilder? = null

        fun getInstance(context: Context): ChatDbBuilder? {
            if (INSTANCE == null) {
                synchronized(ChatDbBuilder::class.java) {
                    INSTANCE = Room.databaseBuilder(context.applicationContext,
                        ChatDbBuilder::class.java, "chat_table")
                        .fallbackToDestructiveMigration()
                        .build()
                }
            }
            return INSTANCE
        }
    }
}

 

  • @Database ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด์„œ ๊ทธ ์•ˆ์— ์ž์‹ ์ด ๋งŒ๋“  entity ํด๋ž˜์Šค๋ฅผ ๋‹ฌ์•„์ฃผ๊ณ , ๋ฒ„์ „๋„ ์„ค์ •ํ•ด์ค€๋‹ค.
  • ์ถ”์ƒ ํด๋ž˜์Šค๋กœ ๋งŒ๋“ค๊ณ , ์ถ”์ƒ ํด๋ž˜์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ์ถ”์ƒ ๋ฉ”์„œ๋“œ chatDao๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.
  • companion object ์•ˆ์—๋‹ค๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋นŒ๋”๋ฅผ ์„ค์ •ํ•ด์ค€๋‹ค.

 


4. Recyclerview ๊ตฌํ˜„

room ๊ธฐ๋Šฅ ๊ตฌํ˜„์ด ๋๋‚ฌ์œผ๋ฏ€๋กœ, ์ด์ œ๋Š” recyclerView๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

 

recyclerview์— ๋Œ€ํ•ด ์ž ๊ณ ํ•  ๋งŒํ•œ ๊ฒƒ๋“ค

https://yunaaaas.tistory.com/43

 

[Android/Kotlin] RecyclerView ๋งŒ๋“ค๊ธฐ

์˜ค๋Š˜์€ ๊ฐ„๋‹จํ•œ ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ ์‹œ๋ฆฌ์ฆˆ 1ํƒ„์ธ RecyclerView ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์†Œ๊ฐœํ•ด๋ณด๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค! RecyclerView ๋ž€?! RecyclerView๋ž€ ? ๋ฐ์ดํ„ฐ ์ง‘ํ•ฉ๋“ค์„ ๊ฐ๊ฐ์˜ ๊ฐœ๋ณ„ ์•„์ดํ…œ ๋‹จ์œ„๋กœ ๊ตฌ์„ฑํ•˜์—ฌ ํ™”๋ฉด์— ์ถœ๋ ฅํ•ด

yunaaaas.tistory.com

 

recyclerview item xml ํŒŒ์ผ

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_margin="10dp"
    app:cardCornerRadius="10dp">

    <LinearLayout
        android:background="#FFF9C4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <de.hdodenhof.circleimageview.CircleImageView
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:layout_marginStart="10dp"
                android:layout_marginTop="10dp"
                android:layout_marginEnd="15dp"
                android:src="@drawable/ic_baseline_account_circle_24"/>

            <TextView
                android:id="@+id/recycler_view_item_id"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="username"
                android:layout_marginTop="5dp"
                android:layout_gravity="center"
                android:textSize="20sp"
                android:fontFamily="@font/sunflower_light"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="15dp"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/recycler_view_item_comment"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="comment"
                android:textSize="25sp"
                android:background="#FFF6A5"
                android:layout_marginEnd="10dp"/>

            <TextView
                android:id="@+id/recycler_view_item_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="time"
                android:textSize="20sp"/>

        </LinearLayout>
    </LinearLayout>

</androidx.cardview.widget.CardView>

 

RecyclerView Adapter ํด๋ž˜์Šค

class ChattingRecyclerAdapter(val context: Context, var chatList: List<ChatEntity>)
    : RecyclerView.Adapter<ChattingRecyclerAdapter.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = RecyclerviewItemChatBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(chatList[position])
    }

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

    @SuppressLint("NotifyDataSetChanged")
    fun setData(chat: List<ChatEntity>) {
        chatList = chat
        notifyDataSetChanged()
    }

    inner class ViewHolder(val binding: RecyclerviewItemChatBinding): RecyclerView.ViewHolder(binding.root) {
        val id = binding.recyclerViewItemId
        val comment = binding.recyclerViewItemComment
        val time = binding.recyclerViewItemTime

        fun bind(chatEntity: ChatEntity) {
            id.text = chatEntity.sendId
            comment.text = chatEntity.comment
            time.text = chatEntity.sendTime
        }
    }
}

์–ด๋Œ‘ํ„ฐ์—๋Š” view binding์„ ์‚ฌ์šฉํ•ด ๋ณด์•˜๋‹ค.

 

5. Activity ๊ตฌํ˜„

recyclerview ๊ตฌํ˜„๋„ ๋งˆ์ณค์œผ๋‹ˆ, ์ด์ œ๋Š” ์•กํ‹ฐ๋น„ํ‹ฐ ๊ตฌํ˜„์„ ํ•  ์ฐจ๋ก€์ด๋‹ค.

 

room, recyclerview ์ƒ์„ฑ

val r = Runnable {
    try {
        chatList = chatDb.chatDao().getAll()
        adapter = ChattingRecyclerAdapter(this, chatList)
        adapter.notifyDataSetChanged()
        recyclerView = binding.chatRecyclerView
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.setHasFixedSize(true)
    } catch (e: Exception) {
        Log.e("ERROR", "$e")
    }
}

room๊ณผ recyclerview๋ฅผ ์ƒ์„ฑํ•  ๋•Œ, Thread ์•ˆ์—๋‹ค๊ฐ€ ๋งŒ๋“ค์–ด ์ฃผ์—ˆ๋‹ค.

 

 

๋ฐ์ดํ„ฐ๋ฅผ ์‚ฝ์ž…ํ•˜๋Š” ์ฝ”๋“œ

val addRunnable = Runnable {
    val newChat = ChatEntity()
    // TODO :: intent ๊ฐ’ ๋ฐ›์•„์˜ค๊ธฐ
    newChat.sendId = intent.getStringExtra("id").toString()
    newChat.comment = binding.chatComment.text.toString()

    // ์‹œ๊ฐ„ ๊ตฌํ•˜๊ธฐ
    val now = System.currentTimeMillis()
    val date = Date(now)
    val dateFormat = SimpleDateFormat("MM/dd hh:ss")
    val getTime = dateFormat.format(date)

    newChat.sendTime = getTime.toString()
    chatDb.chatDao().insert(newChat)
}

val addThread = Thread(addRunnable)
addThread.start()

chatEntity์•ˆ์—๋‹ค๊ฐ€ ๋ฐ์ดํ„ฐ๋“ค์„ ๋„ฃ์–ด์ค€ ํ›„, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์‚ฝ์ž… ํ•ด์ค€๋‹ค.

 

 

์ „์ฒด ์ฝ”๋“œ

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityRoomChatBinding
    private lateinit var chatList: List<ChatEntity>
    private lateinit var chatDb: ChatDbBuilder
    private lateinit var recyclerView: RecyclerView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityRoomChatBinding.inflate(layoutInflater)
        chatDb = ChatDbBuilder.getInstance(this)!!
        var adapter: ChattingRecyclerAdapter
        binding.chatUserId.text = intent.getStringExtra("id").toString()
        Log.d("TAG", "intent putExtra: ${intent.getStringExtra("image")}")

        val r = Runnable {
            try {
                chatList = chatDb.chatDao().getAll()
                adapter = ChattingRecyclerAdapter(this, chatList)
                adapter.notifyDataSetChanged()
                recyclerView = binding.chatRecyclerView
                recyclerView.adapter = adapter
                recyclerView.layoutManager = LinearLayoutManager(this)
                recyclerView.setHasFixedSize(true)
            } catch (e: Exception) {
                Log.e("ERROR", "$e")
            }
        }

        binding.imageSendComment.setOnClickListener {
            if (binding.chatComment.text.isNotEmpty()) {
                addData()
            } else {
                Toast.makeText(this, " ๋น„์—ˆ์–ด์š”", Toast.LENGTH_SHORT).show()
            }
        }

        val thread = Thread(r)
        thread.start()
        setContentView(binding.root)
    }

    private fun addData() {
        val addRunnable = Runnable {
            val newChat = ChatEntity()
            // TODO :: intent ๊ฐ’ ๋ฐ›์•„์˜ค๊ธฐ
            newChat.sendId = intent.getStringExtra("id").toString()
            newChat.comment = binding.chatComment.text.toString()

            // ์‹œ๊ฐ„ ๊ตฌํ•˜๊ธฐ
            val now = System.currentTimeMillis()
            val date = Date(now)
            val dateFormat = SimpleDateFormat("MM/dd hh:ss")
            val getTime = dateFormat.format(date)

            newChat.sendTime = getTime.toString()
            chatDb.chatDao().insert(newChat)
        }

        val addThread = Thread(addRunnable)
        addThread.start()
    }
}

 


๐Ÿ’ก๋Š๋‚€ ์ 

  • ์˜›๋‚ ์—๋Š” ์–ด๋ ค์› ๋˜ room์— ๋Œ€ํ•ด ์ƒˆ๋กœ์šด ๋‚ด์šฉ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๊ณ , ๋” ๋งŽ์€ ๋‚ด์šฉ์„ ์ดํ•ดํ• ์ˆ˜ ์žˆ์—ˆ๋‹ค
  • room์„ ๊ณต๋ถ€ํ•˜๋ฉด์„œ sql ๋ฌธ๋ฒ•์˜ ํ•„์š”์„ฑ(?) ์„ ๋Š๊ผˆ๋‹ค.
  • ๋‚ด๊ฐ€ ์•Œ๊ณ  ์žˆ๋Š” ๋‚ด์šฉ์„ ์„ค๋ช…ํ•˜๊ธฐ๊ฐ€ ์ƒ๊ฐ๋ณด๋‹ค ์–ด๋ ค์› ๋‹ค.
728x90

๋Œ“๊ธ€