전체 코드
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>
|
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 |