💻STUDY/ANDROID STUDY

9주차 스터디 정리

coldNoodlePigeon 2022. 7. 25.

Android와 SQLite 데이터 베이스

 

 

 

 

다음과 같이 kotlin class인 SqliteHelper를 생성해주어 다음과 같이 코드를 입력했다. 

package com.example.a41study.flow9.sqlite

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper

class SqliteHelper(context:Context,name:String,version:Int)
    :SQLiteOpenHelper(context,name,null,version){

    override fun onCreate(p0: SQLiteDatabase?) {
        //name이 없을 경우 호출됨.

        val create="create table memo (`no` integer primary key,content text,datetime integer)"
        //memo라는 table을 생성해라.

        p0?.execSQL(create)
    }

    override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
        //name은 있지만, version이 다른 경우 호출됨.
        //테이블에 변경사항이 있을 경우 호출됨.
        //SqliteHelper()의 생성자를 호출할 때 기존 데이터베이스와 버전을 비교해서 더 높으면 호출됨.
    }
}

SQLiteOpenHelper를 상속받아서 다음과 같은 parameter을 받는다. 

onCreate함수는 name이 없을 경우 호출되어 파일을 생성, onUpgrade는 주석에 달아놓은 설명처럼 다음과 같은 상황일때 호출되어 작동하는 함수이다. 

 

package com.example.a41study.flow9.sqlite

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.a41study.R

class MainActivity : AppCompatActivity() {

    val DB_NAME="sqlite.sql"
    val DB_VERSION=1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val helper=SqliteHelper(this,DB_NAME,DB_VERSION)


    }
}

MainActivity.kt에서는 다음과 같이 코드를 입력하여 class를 사용할 수 있다. 

DB_NAME, DB_VERSION을 전달해준다. 

 

 

package com.example.a41study.flow9.sqlite

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper


data class Memo(var no:Int,var content:String,var datetime:Long)


class SqliteHelper(context:Context,name:String,version:Int)
    :SQLiteOpenHelper(context,name,null,version){

    override fun onCreate(p0: SQLiteDatabase?) {
        //name이 없을 경우 호출됨.

        val create="create table memo (`no` integer primary key,content text,datetime integer)"
        //memo라는 table을 생성해라.

        p0?.execSQL(create)
    }

    override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
        //name은 있지만, version이 다른 경우 호출됨.
        //테이블에 변경사항이 있을 경우 호출됨.
        //SqliteHelper()의 생성자를 호출할 때 기존 데이터베이스와 버전을 비교해서 더 높으면 호출됨.
    }

    //데이터 입력함수
    fun insertMemo(memo:Memo){

    }
}

데이터 입력함수 insertMemo를 주목하자. data class Memo를 생성하여 parameter을 더 편하게 표현할 수 있다. 

 

package com.example.a41study.flow9.sqlite

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.a41study.R

class MainActivity : AppCompatActivity() {

    val DB_NAME="sqlite.sql"
    val DB_VERSION=1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val helper=SqliteHelper(this,DB_NAME,DB_VERSION)

        val memo=Memo(1, "내용",12341251)
        helper.insertMemo(memo)
    }
}

MainActivity에서는 다음과 같이 표현할 수 있다. memo라는 val을 생성하여 parameter로 insertMemo함수에 매개변수로 넣어 사용하는 방식으로 가능하다. 

 

 

 

//데이터 입력함수
    fun insertMemo(memo:Memo){
        //db 가져오기
        val wd = writableDatabase

        //db에 넣기
        wd.insert(테이블이름, null,실제 값)

        //db 닫기
        wd.close()

    }

 

//데이터 조회함수
    fun selectMemo():MutableList<Memo>{
        val list=mutableListOf<Memo>()

        val select="select * from memo"
        //memo 테이블에서 select를 모든 column으로.

        val rd=readableDatabase
        val cursor= rd.rawQuery(select,null)


        //cursor은 일종의 리스트 같은 것. 반복문을 통해 꺼낼 수 있음.
        while(cursor.moveToNext()){
            val no=cursor.getLong(cursor.getColumnIndex("no"))
            val content=cursor.getString(cursor.getColumnIndex("content"))
            val datetime=cursor.getLong(cursor.getColumnIndex("datetime"))

            val memo=Memo(no,content,datetime)
            list.add(memo)
        }

        //return전에 close해줘야함.
        cursor.close()
        rd.close()

        return list
    }

이러면 curwor에서 getLong을 하는 과정에서 오류가 발생한다. 

 

그래서 아래와 같이 index라는 변수를 만들어주어서 넣어주었다. 

//데이터 조회함수
    fun selectMemo():MutableList<Memo>{
        val list=mutableListOf<Memo>()

        val select="select * from memo"
        //memo 테이블에서 select를 모든 column으로.

        val rd=readableDatabase
        val cursor= rd.rawQuery(select,null)


        //cursor은 일종의 리스트 같은 것. 반복문을 통해 꺼낼 수 있음.
        while(cursor.moveToNext()){
            val index1= cursor.getColumnIndex("no")
            val index2 = cursor.getColumnIndex("content")
            val index3= cursor.getColumnIndex("datetime")
            
            val no=cursor.getLong(index1)
            val content=cursor.getString(index2)
            val datetime=cursor.getLong(index3)

            val memo=Memo(no,content,datetime)
            list.add(memo)
        }

        //return전에 close해줘야함.
        cursor.close()
        rd.close()

        return list
    }

 

//데이터 수정 함수
    fun updateMemo(memo:Memo){
        val wd=writableDatabase

        //Memo를 입력타입으로 변환
        val values=ContentValues()
        values.put("content",memo.content)
        values.put("datetime",memo.datetime)


        wd.update("memo",values,"no = ${memo.no}",null)
        //whereClause: 몇번째 cursor에 있는 데이터를 수정할 것인가
        //table, 수정할 값, 어디를 수정할 것인가(no)
        wd.close()

    }

 

 

//데이터 삭제 함수
    fun deleteMemo(memo:Memo){

        val wd=writableDatabase


        val delete="delete from memo where no=${memo.no}"
        //memo 테이블에서 no가 memo.no와 같은 데이터를 삭제해라.
        wd.execSQL(delete)

        //위에 두줄 대신 아래 한줄로 처리가 가능.
        wd.delete("memo","no=${memo.no}",null)


        wd.close()
    }

 

 

다음은 RecyclerAdapter.kt를 새로 생성하여 다음과 같이 RecyclerView의 어댑터 코드를 작성한다. 

package com.example.a41study.flow9.sqlite

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.a41study.R
import java.text.SimpleDateFormat

class RecyclerAdapter : RecyclerView.Adapter<Holder>(){
    val listData = mutableListOf<Memo>()


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val view= LayoutInflater.from(parent.context)
            .inflate(R.layout.item_recycler,parent,false)

        return Holder(view)
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        //실제 화면에 그려주는 것.
        val memo=listData.get(position) //현재 위치를 꺼냄
        holder.setMemo(memo)
    }

    override fun getItemCount(): Int {
        return listData.size //몇개의 data를 return할 것이다.
    }
}

class Holder(itemView: View): RecyclerView.ViewHolder(itemView){
    fun setMemo(memo:Memo){
        //textNo가 import가 안되네요..... ;;
        itemView.textNo.text="${memo.no}"
        itemView.textContent.text="${memo.content}"

        //SimpleDataFormat을 통해 yyyy/MM,,처럼 형식을 정해주고 return 가능.
        val sdf=SimpleDateFormat("yyyy/MM/dd hh:mm")
        val datetime= sdf.format(memo.datetime)

        itemView.textDatetime.text="$datetime"
    }
}

여기서 textNo랑 textContext가 import가 안되는 상황이 발생하는데 강의가 2020년도 강의라 업데이트 이후로 사라졌나 보다.. 좀 더 찾아봐야할듯 ; 

 

 

다음과 같이 activity_main을 구성한 후, 

 

package com.example.a41study.flow9.sqlite

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.a41study.R

class MainActivity : AppCompatActivity() {

    val DB_NAME="sqlite.sql"
    val DB_VERSION=1

    val recyclerMemo:RecyclerView =findViewById(R.id.recyclerMemo)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val helper=SqliteHelper(this,DB_NAME,DB_VERSION)

        val adapter = RecyclerAdapter()


        val memos = helper.selectMemo()
        adapter.listData.addAll(memos)

        recyclerMemo.adapter=adapter
        recyclerMemo.layoutManager=LinearLayoutManager(this)



    }
}

MainActivity에서 listData에 addAll을 통해 data를 추가시키는 코드를 작성한다. 

recyclerMemo가 호출이 안되길래 위에서 변수 선언을 따로 헤주었다. (이렇게 하는게 맞는지 모르겠다...) 

 

package com.example.a41study.flow9.sqlite

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.a41study.R

class MainActivity : AppCompatActivity() {

    val DB_NAME="sqlite.sql"
    val DB_VERSION=1

    val recyclerMemo:RecyclerView =findViewById(R.id.recyclerMemo)
    val buttonSave:Button=findViewById(R.id.buttonSave)
    val editMemo:EditText=findViewById(R.id.editMemo)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val helper=SqliteHelper(this,DB_NAME,DB_VERSION)

        val adapter = RecyclerAdapter()


        val memos = helper.selectMemo()
        adapter.listData.addAll(memos)

        recyclerMemo.adapter=adapter
        recyclerMemo.layoutManager=LinearLayoutManager(this)

        buttonSave.setOnClickListener {
            val content= editMemo.text.toString()
            if(content.isNotEmpty()){
                val memo = Memo(null,content,System.currentTimeMillis())
                helper.insertMemo(memo) //database에 data가 들어간 상태.

                //기존 작성글 삭제
                editMemo.setText("")

                //목록 갱신
                adapter.listData.clear() //기존 목록 다 지우고
                adapter.listData.addAll(helper.selectMemo()) //다시 다 가져와서
                adapter.notifyDataSetChanged() //갱신 
            }
        }


    }
}

그리고 버튼을 눌렀을때 content가 empty가 아니면 해당 data 정보를 insertMemo하도록 하였다. 

그리고 기존 작성글을 삭제 후 목록이 실시간으로 갱신되도록 하기 위해 

기존목록을 먼저 다 지우고 다시 다 가져와서 갱신하도록 했다. (실제로는 이런식으로 코드를 짜지 않는다고 함.) 

 

 

Room 데이터베이스 

 

먼저 build gradle 에 다음과 같이 코드를 추가해주었다. 

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
}

kotlin-kapt를 추가

 

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.example.a42study"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }

    buildFeatures{
        viewBinding true
    }
}

viewBinding true를 추가 

 

dependencies {
    //def는 변수 선언하는 키워드
    def roomVersion = "2.4.2"

    implementation("androidx.room:room-runtime:$roomVersion")
    annotationProcessor("androidx.room:room-compiler:$roomVersion")

    // To use Kotlin annotation processing tool (kapt)
    kapt("androidx.room:room-compiler:$roomVersion")

    // optional - Kotlin Extensions and Coroutines support for Room
    implementation("androidx.room:room-ktx:$roomVersion")


    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.2'
    implementation 'com.google.android.material:material:1.6.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

room 버전은 2022 7월 24일 기준으로 현재 2.4.2이므로 다음과 같이 입력해서 추가함. 

 

https://developer.android.com/jetpack/androidx/releases/room?hl=ko#kts

△여기에서 필요한 코드를 복사해서 붙여넣었다. 

 

다음 activity_main.xml을 다음과 같이 편집 후, (item_recycler.xml 또한 마찬가지로..) 

 

 

아래의 경로로 kotlin 파일을 더 생성한다. 

 

 

package com.example.a42study

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "room_memo")
//table로 요 밑에 class를 사용하겠다는 의미.
class RoomMemo {
    @PrimaryKey(autoGenerate = true) //no에 값이 없을때 자동증가된 숫자값을 db에 입력해줌.
    @ColumnInfo //변수 하나하나 모두 column으로 취급한다는 의미.
    var no: Long? =null //처음 생성한건 null일 수 있음.
    @ColumnInfo
    var content: String=""
    @ColumnInfo(name="date") //코드에서는 datetime으로 쓰고, database에서는 date라고 표시.
    var datetime: Long=0

    constructor(content:String,datetime:Long){ //생성자 사용. (코드 간결화 위해) 
        this.content=content
        this.datetime=datetime
    }
}

RoomMemo라는 table를 생성해준다. (SQLite에서 했던 방식과 비슷) 

 

package com.example.a42study

import androidx.room.*

@Dao //Dao라는 것을 innotation을 붙여서 알려줌.
interface RoomMemoDAO {
    @Query("select * from room_memo" )//room_memo에서 전부 다 긁어오라는 의미.
    fun getAll():List<RoomMemo>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(memo:RoomMemo)

    @Delete //innotation을 이렇게 붙여주면 자동으로 필요한 코드를 생성해줌.
    fun delete(memo:RoomMemo)
}

RoomMemoDAO.kt.

 

 

 

package com.example.a42study

import androidx.room.Database
import androidx.room.RoomDatabase

//직접 사용x 추상 클래스로 만들어줌.
//이 database에서 사용할 수 있는 DAO클래스(들)을 꺼내서 사용할 수 있게 해줌. (함수들의 모음)

@Database(entities = arrayOf(RoomMemo::class),version=1, exportSchema = false)
//지금은 table이 하나지만 table이 여러개일경우 쉼표로 구분해서 넣어줌.
abstract class RoomHelper:RoomDatabase() {
    abstract fun roomMemoDao(): RoomMemoDAO //room이 알아서 코드를 생성해줌. 
}

 

 

package com.example.a42study

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.a42study.databinding.ItemRecyclerBinding
import java.text.SimpleDateFormat

class RecyclerAdapter(val roomMemoList:List<RoomMemo>): RecyclerView.Adapter<RecyclerAdapter.Holder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val binding = ItemRecyclerBinding.inflate(
            LayoutInflater.from(parent.context),parent,false)
        //parent는 View. View에서 context 바로 꺼낼 수 있음.

        return Holder(binding)
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        holder.setMemo(roomMemoList.get(position))
    }

    override fun getItemCount()=roomMemoList.size

    class Holder(val binding: ItemRecyclerBinding):RecyclerView.ViewHolder(binding.root){

        fun setMemo(roomMemo:RoomMemo){
            with(binding){
                textNo.text="${roomMemo.no}"
                textContent.text=roomMemo.content

                //날짜 형태로 변환(왜냐하면 Long형으로 받으니까..)
                val sdf=SimpleDateFormat("yyyy/MM/dd hh:mm")
                textDatetime.text=sdf.format(roomMemo.datetime)
            }
        }
    }
}

RecyclerAdapter 생성 

 

 

package com.example.a42study

import androidx.room.*

@Dao //Dao라는 것을 innotation을 붙여서 알려줌.
interface RoomMemoDAO {
    @Query("select * from room_memo" )//room_memo에서 전부 다 긁어오라는 의미.
    fun getAll():List<RoomMemo>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(memo:RoomMemo)

    @Delete //innotation을 이렇게 붙여주면 자동으로 필요한 코드를 생성해줌.
    fun delete(memo:RoomMemo)
}

RoomMemoDAO 

 

 

package com.example.a42study

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.room.Room
import com.example.a42study.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    val binding by lazy{ActivityMainBinding.inflate(layoutInflater)}
    lateinit var helper:RoomHelper
    lateinit var memoAdapter:RecyclerAdapter
    val memoList = mutableListOf<RoomMemo>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        helper= Room.databaseBuilder(this,RoomHelper::class.java,"room_db")
            .allowMainThreadQueries() //공부할때만 쓴다.
            .build()


        memoAdapter= RecyclerAdapter(memoList)

        refreshAdapter()

        with(binding){
            recyclerMemo.adapter=memoAdapter
            recyclerMemo.layoutManager=LinearLayoutManager(this@MainActivity)

            buttonSave.setOnClickListener {
                val content=editMemo.text.toString()
                if(content.isNotEmpty()){
                    val datetime=System.currentTimeMillis()
                    val memo=RoomMemo(content,datetime)
                    helper.roomMemoDao().insert(memo)

                    refreshAdapter()

                }
            }
        }
    }

    fun refreshAdapter(){
        memoList.clear()
        memoList.addAll(helper.roomMemoDao().getAll())
        memoAdapter.notifyDataSetChanged()
    }
}

MainActivity.kt 

'💻STUDY > ANDROID STUDY' 카테고리의 다른 글

10주차 스터디 정리  (0) 2022.08.01
8주차 스터디 정리  (0) 2022.07.18
[Android-Kotlin] 7주차  (0) 2022.05.25
[Android-Kotlin] 6주차  (0) 2022.05.16
[Android-Kotlin] 5주차  (0) 2022.05.09

댓글