Design-loving front-end engineer
Ryong
Design-loving front-end engineer
전체 방문자
오늘
어제
    • Framework
    • React
      • Concept
      • Library
      • Hook
      • Component
      • Test
    • NodeJS
    • Android
      • Concept
      • Code
      • Sunflower
      • Etc
    • Flutter
      • Concept
      • Package
    • Web
    • Web
    • CSS
    • Language
    • JavaScript
    • TypeScript
    • Kotlin
    • Dart
    • Algorithm
    • Data Structure
    • Programmers
    • Management
    • Git
    • Editor
    • VSCode
    • Knowledge
    • Voice
Design-loving front-end engineer

Ryong

[Android] [Kotlin] SQLite
Android/Concept

[Android] [Kotlin] SQLite

2021. 9. 14. 23:01

전체 코드

MainActivity.kt

더보기
class MainActivity : AppCompatActivity() {

    val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

    val helper = SqliteHelper(this, "memo", 1)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        val adapter = RecyclerAdapter()
        adapter.helper = helper

        adapter.listData.addAll(helper.selectMemo())
        binding.recyclerMemo.adapter = adapter
        binding.recyclerMemo.layoutManager = LinearLayoutManager(this)

        binding.buttonSave.setOnClickListener {
            if (binding.editMemo.text.toString().isNotEmpty()) {
                val memo = Memo(null, binding.editMemo.text.toString(), System.currentTimeMillis())
                helper.insertMemo(memo)

                adapter.listData.clear()
                adapter.listData.addAll(helper.selectMemo())
                adapter.notifyDataSetChanged()
                binding.editMemo.setText("")
            }
        }
    }
}

 

SqliteHelper.kt

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

    override fun onCreate(db: SQLiteDatabase?) {
        val create = "create table memo (" +
                "no integer primary key, " +
                "content text, " +
                "datetime integer" +
                ")"
        db?.execSQL(create)
    }

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { }

    fun insertMemo(memo:Memo) {
        val values = ContentValues()
        values.put("content", memo.content)
        values.put("datetime", memo.datetime)

        val wd = writableDatabase
        wd.insert("memo", null, values)
        wd.close()
    }

    fun selectMemo(): MutableList<Memo> {
        val list = mutableListOf<Memo>() 
        val select = "select * from memo" 
        val rd = readableDatabase  
        val cursor = rd.rawQuery(select, null) 
        while (cursor.moveToNext()) {  
            val no = cursor.getLong(cursor.getColumnIndex("no"))
            val content = cursor.getString(cursor.getColumnIndex("content"))
            val datetime = cursor.getLong(cursor.getColumnIndex("datetime"))

            list.add(Memo(no, content, datetime))
        }

        cursor.close()
        rd.close()
        return list
    }

    fun updateMemo(memo:Memo) {
        val values = ContentValues()
        values.put("content", memo.content)
        values.put("datetime", memo.datetime)

        val wd = writableDatabase
        wd.update("memo", values, "no = ${memo.no}", null)
        wd.close()
    }

    fun deleteMemo(memo:Memo) {
        val delete = "delete from memo where no = ${memo.no}"
        val db = writableDatabase
        db.execSQL(delete)
        db.close()
    }
}

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

 

RecyclerAdapter

더보기
class RecyclerAdapter : RecyclerView.Adapter<RecyclerAdapter.Holder>() {
    var helper:SqliteHelper? = null
    var listData = mutableListOf<Memo>()
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val binding = ItemRecyclerBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return Holder(binding)
    }
    override fun getItemCount(): Int {
        return listData.size
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        val memo = listData.get(position)
        holder.setMemo(memo)
    }

    inner class Holder(val binding: ItemRecyclerBinding) : RecyclerView.ViewHolder(binding.root) {
        var mMemo:Memo? = null
        init {
            binding.buttonDelete.setOnClickListener {
                helper?.deleteMemo(mMemo!!)                
                listData.remove(mMemo)
                notifyDataSetChanged()
            }
        }
        fun setMemo(memo:Memo) {
            binding.textNo.text = "${memo.no}"
            binding.textContent.text = memo.content
            val sdf = SimpleDateFormat("yyyy/MM/dd hh:mm")            
            binding.textDatetime.text = "${sdf.format(memo.datetime)}"

            this.mMemo = memo
        }
    }
}

 

activity_main.xml

더보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?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/recyclerMemo"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="15dp"
        app:layout_constraintBottom_toTopOf="@+id/editMemo"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
 
    <EditText
        android:id="@+id/editMemo"
        android:layout_width="0dp"
        android:layout_height="100dp"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="8dp"
        android:ems="10"
        android:hint="메모를 입력하세요"
        android:inputType="textMultiLine"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/buttonSave"
        app:layout_constraintStart_toStartOf="parent" />
 
    <Button
        android:id="@+id/buttonSave"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="8dp"
        android:text="저장"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
 
</androidx.constraintlayout.widget.ConstraintLayout>
cs

 

item_recycler.xml

더보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?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="wrap_content">
 
    <TextView
        android:id="@+id/textNo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginBottom="32dp"
        android:text="01"
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
 
    <TextView
        android:id="@+id/textContent"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginBottom="32dp"
        android:ellipsize="end"
        android:gravity="center_vertical"
        android:text="메모 내용 표시"
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/buttonDelete"
        app:layout_constraintStart_toEndOf="@+id/textNo"
        app:layout_constraintTop_toTopOf="parent" />
 
    <TextView
        android:id="@+id/textDatetime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:text="2020/01/01 13:57"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textContent" />
 
    <Button
        android:id="@+id/buttonDelete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="32dp"
        android:text="삭제"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
 
</androidx.constraintlayout.widget.ConstraintLayout>
Colored by Color Scripter
cs

 

UI

 

SQLite 데이터베이스

✔  관계형 데이터베이스

SQLiteOpenHelper

✔  데이터베이스를 파일로 생성하고 코틀린 코드에서 사용할 수 있도록 데이터베이스와 연결하는 역할

SqliteHelper 클래스 생성

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

    override fun onCreate(db: SQLiteDatabase?) {
       
    }

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
    
    }
}

◽  onUpgrade() 함수는 SqliteHelper에 전달되는 버전 정보가 변경되었을 때, 현재 생성되어 있는 데이터베이스의 버전과 비교해서 더 높을 때 사용한다.

 

테이블 생성

override fun onCreate(db: SQLiteDatabase?) {
    val create = "create table memo (" +
        "no integer primary key, " +
        "content text, " +
        "datetime integer" +
        ")"
    db?.execSQL(create)
}

◽  테이블 생성 쿼리를 문자열로 입력한 후, db의 execSQL() 메서드에 전달해서 테이블을 생성한다.

◽  데이터베이스가 이미 생성되어 있다면 더 이상 실행되지 않는다.

 

데이터 전달할 데이터 클래스 생성

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

◽  데이터베이스를 생성할 때, no와 datetime을 integer로 설정했으나, 숫자의 범위가 서로 달라서 클래스 생성 시에는 Long으로 받는다.

◽  앞으로 특별한 이유가 없다면 SQLite에서 integer로 선언한 것은 소스 코드에서 Long으로 사용해야 한다.

◽  no만 null을 허용한 이유는 no는 Primary key로 지정되어 값이 자동으로 증가하기 때문에 데이터 삽입 시에는 필요하지 않기 때문이다.

 

INSERT 메서드

fun insertMemo(memo: Memo) {
    // SQLiteOpenHelper를 이용한 값 입력 시, Map 클래스와 비슷한 ContentValues 클래스를 이용
    val values = ContentValues()
    values.put("content", memo.content)
    values.put("datetime", memo.datetime)

    val wd = writableDatabase  // 데이터베이스 불러오기
    wd.insert("memo", null, values)  // 값 입력하기
    wd.close()  // 닫아주기
}

 

SELECT 메서드

fun selectMemo(): MutableList<Memo> {
    val list = mutableListOf<Memo>()  // 반환할 값을 변수로 선언
    val select = "select * from memo"  // 메모의 전체 데이터를 조회하는 쿼리를 작성
    val rd = readableDatabase  // 읽기 전용 데이터베이스 불러오기
    val cursor = rd.rawQuery(select, null)  // 작성해둔 쿼리를 담아서 커서를 불러온다.
    while (cursor.moveToNext()) {  // 모든 레코드를 읽을 때까지 반복하고, 레코드가 없으면 반복문을 빠져나간다.
        // 3개의 컬럼에서 값을 꺼낸 후 변수에 담기
        val no = cursor.getLong(cursor.getColumnIndex("no"))
        val content = cursor.getString(cursor.getColumnIndex("content"))
        val datetime = cursor.getLong(cursor.getColumnIndex("datetime"))

        list.add(Memo(no, content, datetime))  // 저장된 값들로 Memo 클래스 생성하고 목록에 더하기
    }

    cursor.close()  // 커서 닫기
    rd.close()  // 데이터베이스 닫기 (메모리 누수 방지)
    return list
}

◽  moveToNext() : 다음 줄에 사용할 수 있는 레코드가 있는지 여부를 반환한다.

 

UPDATE 메서드

fun updateMemo(memo: Memo) {
    val values = ContentValues()
    values.put("content", memo.content)
    values.put("datetime", memo.datetime)

    val wd = writableDatabase
    wd.update("memo", values, "no = ${memo.no}", null)
    wd.close()
}

◽  update(테이블 이름, update 해줄 값, 수정할 조건)

◽  수정할 조건(WhereClause) : 몇 번째 커서에 있는 값을 수정할 것인지를 Primary Key로 지정한 속성을 사용한다.

 

DELETE 메서드

fun deleteMemo(memo: Memo) {
    val delete = "delete from memo where no = ${memo.no}"  // 삭제 쿼리 작성
    val db = writableDatabase
    db.execSQL(delete)  // 쿼리 직접 실행
    db.close()
}

 

Adapter

아이템을 출력하기 위한 어댑터 만들기

class RecyclerAdapter : RecyclerView.Adapter<RecyclerAdapter.Holder>() {

    var listData = mutableListOf<Memo>()
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val binding = ItemRecyclerBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return Holder(binding)
    }
    
    override fun getItemCount(): Int {
        return listData.size
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        val memo = listData.get(position)
        holder.setMemo(memo)
    }

    inner class Holder(val binding: ItemRecyclerBinding) : RecyclerView.ViewHolder(binding.root) {
        fun setMemo(memo: Memo) {
            binding.textNo.text = "${memo.no}"
            binding.textContent.text = memo.content
            val sdf = SimpleDateFormat("yyyy/MM/dd hh:mm")
            binding.textDatetime.text = "${sdf.format(memo.datetime)}"
        }
    }
}

 

MainActivity

액티비티와 어댑터 연결하기

class MainActivity : AppCompatActivity() {

    val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    val helper = SqliteHelper(this, "memo", 1)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        val adapter = RecyclerAdapter()  // 어댑터 생성
        
        // adapter의 lsitData에 데이터베이스에서 가져온 데이터를 세팅
        adapter.listData.addAll(helper.selectMemo())
        
        binding.recyclerMemo.adapter = adapter  // 리사이클러뷰에 어댑터 연결
        binding.recyclerMemo.layoutManager = LinearLayoutManager(this)  // 레이아웃 매니저 설정

        binding.buttonSave.setOnClickListener {
            if (binding.editMemo.text.toString().isNotEmpty()) {
                val memo = Memo(null, binding.editMemo.text.toString(), System.currentTimeMillis())
                helper.insertMemo(memo)  // 데이터베이스에 저장

                adapter.listData.clear()  // 어댑터 데이터 초기화
                
                // 데이터베이스에서 추가된 새로운 목록을 읽어와서 어댑터의 데이터를 재세팅
                adapter.listData.addAll(helper.selectMemo())
                adapter.notifyDataSetChanged()  // 변경된 사항 적용
                
                binding.editMemo.setText("")  // EditText 내용 초기화
            }
        }
    }
}

 

Adapter

아이템뷰에 구현한 삭제 버튼 구현하기 : 어댑터 클래스

class RecyclerAdapter : RecyclerView.Adapter<RecyclerAdapter.Holder>() {

//    var listData = mutableListOf<Memo>()
    var helper: SqliteHelper? = null
    
//    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
//        val binding = ItemRecyclerBinding.inflate(LayoutInflater.from(parent.context), parent, false)
//        return Holder(binding)
//    }
//    override fun getItemCount(): Int {
//        return listData.size
//    }

//    override fun onBindViewHolder(holder: Holder, position: Int) {
//        val memo = listData.get(position)
//        holder.setMemo(memo)
//    }

    inner class Holder(val binding: ItemRecyclerBinding) : RecyclerView.ViewHolder(binding.root) {
        var mMemo:Memo? = null  // 받아온 Memo를 setMemo 메서드 밖에서 사용하기 위해
        
        init {
            binding.buttonDelete.setOnClickListener {
                helper?.deleteMemo(mMemo!!)  // 데이터베이스에서 해당 Memo 삭제
                
                listData.remove(mMemo)  // 어댑터의 리스트에서도 해당 Memo 삭제
                notifyDataSetChanged()  // 변경된 리스트 내용 적용
            }
        }
        
        fun setMemo(memo: Memo) {
//            binding.textNo.text = "${memo.no}"
//            binding.textContent.text = memo.content
//            val sdf = SimpleDateFormat("yyyy/MM/dd hh:mm")
//            binding.textDatetime.text = "${sdf.format(memo.datetime)}"

            this.mMemo = memo
//        }
    }
}

◽  helper?.deleteMemo(mMemo!!)에서 !!를 선언한 이유 : deleteMemo()에서 null을 허용하지 않았지만, Holer의 mMemo는 null을 허용하기 때문에 !!을 사용해서 강제 사용

 

MainActivity

아이템뷰에 구현한 삭제 버튼 구현하기 : MainActivity

class MainActivity : AppCompatActivity() {

//    val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
//    val helper = SqliteHelper(this, "memo", 1)

    override fun onCreate(savedInstanceState: Bundle?) {
//        super.onCreate(savedInstanceState)
//        setContentView(binding.root)
//
//        val adapter = RecyclerAdapter()
        adapter.helper = helper  // 어댑터에 데이터베이스를 연결

//        adapter.listData.addAll(helper.selectMemo())
//        binding.recyclerMemo.adapter = adapter
//        binding.recyclerMemo.layoutManager = LinearLayoutManager(this)
//
//        binding.buttonSave.setOnClickListener {
//            if (binding.editMemo.text.toString().isNotEmpty()) {
//                val memo = Memo(null, binding.editMemo.text.toString(), System.currentTimeMillis())
//                helper.insertMemo(memo)
//
//                adapter.listData.clear()
//                adapter.listData.addAll(helper.selectMemo())
//                adapter.notifyDataSetChanged()
//                binding.editMemo.setText("")
//            }
//        }
    }
}
저작자표시

'Android > Concept' 카테고리의 다른 글

[Android] [Kotlin] Room 데이터베이스  (0) 2021.09.19
[Android] [Kotlin] SharedPreferences  (0) 2021.09.19
[Android] [Kotlin] ViewPager (뷰페이저)  (0) 2021.09.13
[Android] [Kotlin] 인텐트  (0) 2021.09.12
[Android] [Kotlin] 생명주기  (0) 2021.09.12
    'Android/Concept' 카테고리의 다른 글
    • [Android] [Kotlin] Room 데이터베이스
    • [Android] [Kotlin] SharedPreferences
    • [Android] [Kotlin] ViewPager (뷰페이저)
    • [Android] [Kotlin] 인텐트
    Design-loving front-end engineer
    Design-loving front-end engineer
    디자인에 관심이 많은 모바일 앱 엔지니어 Ryong입니다.

    티스토리툴바