1.  화면 만들기


1) activity_main.xml 편집하기

 

  📍  파일의 [Design] 모드에서 기존의 텍스트뷰는 지우고 팔레트의 컨테이너 카테고리에서 리사이클뷰를 선택해 화면 상단에 배치

  •  id 속성에 'recyclerMemo'를 입력

  📍  팔레트의 텍스트 카테고리에서 플레인텍스트를 드래그해서 화면 하단에 배치

  •  id 속성은 'editMemo', text 속성은 지우고, hint 속성을 '메모를 입력하세요'라고 수정
  • 여러 줄을 입력할 수 있어야 하므로 inputType 속성의 깃발를 눌러 textMultiLine을 'true'로 변경
  • textPersonName도 그대로 체크 상태로 두고 [Apply] 클릭

  📍  팔레트의 버튼 카테고리에서 버튼을 하나 드래그해서 우측 하단에 가져다 둠

  •  id 속성에는 'buttonSave', text 속성에는 '저장'이라고 입력

 


 

2)  item_recycler.xml 추가하기

 

리사이클러뷰의 아이템 용도로 사용할 item_recycler.xml 레이아웃 파일을 생성해서 편집

 

  📍  [app] - [res] - [layout] 디렉토리에서 새 리소스 파일을 생성

 

  📍  레이아웃 파일이 생성되면 [Design]모드에서 컴포넌트 트리의 최상위 컨스트레이트 레이아웃을 클릭

  • layout_height 속성을 '100dp'로 수정해서 아이템의 높이를 미리 정함

  📍  번호와 메모의 내용을 표시할 텍스트뷰 2개와 날짜를 표시할 텍스트뷰을 하나 배치

  • 번호를 표시할 텍스트뷰 : id - textNo, text = 01
  • 내용을 표시할 텍스트뷰 : id - textContent, maxLines - 2, ellipsize - end, gravity - center_vertical, text - 메모 내용 표시
  • 날짜를 표시할 텍스트뷰 : id - textDatetime, text - 2024/01/01 12:12
  • maxLines속성이 2인데, 두 줄을 넘어가면 말줄임표(...)가 나오도록 하는 속성

 


2.  소스 코드 연결하기

레이아웃과 소스코드를 연결. 코드에서 binding을 사용하므로 build.gradle 파일에 viewBinding 설정을 추가.

viewBinding {
    enabled = true
}

 


1)  RecyclerAdapter 클래스 만들기


Memo 클래스를 데이터로 사용하는 RecyclerAdapter 클래스를 정의

[app] - [java] 밑에 있는 패키지에 RecyclerAdapter라는 이름의 클래스를 생성

class RecyclerAdapter : RecyclerView.Adapter<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 onBindViewHolder(holder: Holder, position: Int) {
        // 뷰 홀더가 재사용될 때 호출
        val memo = listData[position]
        holder.setMemo(memo)
    }

    override fun getItemCount(): Int { // 어댑터에서 관리하는 아이템의 개수
       return listData.size
    }
}

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

 


2)  MainActivity에서 코드 조합하기

onCreate()
class MainActivity : AppCompatActivity() {
    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater)}
    private val helper = SQLiteHelper(this, "memo", 1)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        
        val adapter = RecyclerAdapter()
        adapter.listData.addAll(helper.selectMemo())

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

    }
}

 

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

 

  👩🏻‍💻  클래스 코드 블럭 맨 윗줄에서 바인딩을 생성하고 binding 변수에 저장. 그리고 아랫줄에 SQLiteHelper를 생성하고 변수에 저장

 

setContentView(binding.root)
val adapter = RecyclerAdapter()

 

  👩🏻‍💻  onCreate()의 setContentView에 binding.root를 전달하고, 다음 줄에서 RecyclerAdapter를 생성

 

adapter.listData.addAll(helper.selectMemo())

 

  👩🏻‍💻  adapter의 listData에 데이터베이스에서 가져온 데이터를 세팅

 

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

 

  👩🏻‍💻  화면의 리사이클러뷰 위젯에 adapter를 연결하고 레이아웃 매니저를 설정

 


저장 버튼에 클릭리스너
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("")
    }
}

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

 

  👩🏻‍💻  메모를 입력하는 플레인텍스트를 검사해서 값이 있으면 해당 내용으로 Memo 클래스를 생성

 

helper.insertMemo(memo)

 

  👩🏻‍💻  helper 클래스의 insertMemo() 메서드에 앞에서 생성한 Memo를 전달해 데이터베이스에 저장

 

adapter.listData.clear()

 

  👩🏻‍💻  어댑터의 데이터를 모두 초기화

 

adapter.listData.addAll(helper.selectMemo())
adapter.notifyDataSetChanged()

 

  👩🏻‍💻  데이터베이스에서 새로운 목록을 읽어와 어댑터에 세팅하고 갱신. 새로 생성되는 메모에는 번호가 자동으로 입력되므로 번호를 갱신하기 위해서 새로운 데이터를 세팅하는 것

 

binding.editMemo.setText("")

 

  👩🏻‍💻  메모 내용을 입력하는 위젯의 내용을 지워서 초기화

 


3.  삭제 버튼 추가하기

메모 목록에 삭제 버튼을 추가하여 메모를 삭제할 수 있도록 만듦


1) item_recycler.xml 파일을 열고 목록 아이템의 우측에 삭제 버튼을 배치

  • id : buttonDelete
  • text : 삭제

 


 

2) 메모를 삭제하려면 SQLite의 데이터와 어댑터에 있는 Memo 컬렉션의 데이터를 삭제


  ✓  SQLite의 데이터를 삭제하기 위해서 MainActivity.kt를 열고 클래스의 두 번째 줄에 생성해 둔 helper를 어댑터에 전달
  ✓  어댑터 생성 코드 바로 아랫줄에 작성하는데 어댑터에는 아직 helper 프로퍼티가없기 때문에 빨간색으로 나타남

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

    val adapter = RecyclerAdapter()
    adapter.helper = helper // 추가 코드

3) RecyclerAdapter.kt를 열고 클래스 블록 가장 윗줄에 helper 프로퍼티를 만듦

class RecyclerAdapter : RecyclerView.Adapter<Holder>() {
    var helper: SQLiteHelper? = null // 추가 코드
    val listData = mutableListOf<Memo>()

 


4) RecyclerAdapter.kt의 Holder 클래스에 init 블록을 만듦

 

  ✓  추가한 buttonDetele에 클릭리스너를 달아줌

class Holder(val binding: ItemRecyclerBinding) :RecyclerView.ViewHolder(binding.root) {
    init {
        binding.buttonDelete.setOnClickListener { }
    }

 


 

삭제 버튼을 클릭하면 어댑터의 helper와 listData에 접근해야 되는데, 지금은 어댑터 밖에 Holder 클래스가 있기 때문에 접근할 수 없음. Holder 클래스 전체를 어댑터 클래스 안으로 옮기고 class 앞에 inner 키워드를 붙여줌

  ➡️  Holder 클래스의 위치가 바뀌었기 때문에 RecyclerAdapter.Holder의 제네릭을 다시 import

inner class Holder(val binding: ItemRecyclerBinding: RecyclerView.ViewHolder(binding.root) {
    init {
        binding.buttonDelete.setOnClickListener {
    }
}

fun setMemo(memo: Memo) {
    binding.textNo.text = "${memo.num}"
    binding.textContent.text = memo.content
    val sdf = SimpleDateFormat("yyyy/MM/dd hh:mm")
    binding.textDatetime.text = sdf.format(memo.datetime)
}

 

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

 


홀더는 한 화면에 그려지는 개수만큼 만든 후 재사용을 함으로 1번 메모가 있는 홀더를 스크롤해서 위로 올리면 아래에서 새로운 메모가 1번 홀더를 재사용하는 구조, 따라서 클릭하는 시에 어떤 데이터가 있는지 알야야 하므로 Holder 클래스의 init 위에 변수를 하나 선언하고 setMemo() 메서드로 넘어온 Memo를 임시로 저장

inner class Holder(val binding: ItemRecyclerBinding) :RecyclerView.ViewHolder(binding.root) {
    private var mMemo: Memo? = null
    init {
        binding.buttonDelete.setOnClickListener {}
    }

    fun setMemo(memo: Memo) {
        binding.textNo.text = "${memo.num}"
        binding.textContent.text = memo.content
        val sdf = SimpleDateFormat("yyyy/MM/dd hh:mm")
        binding.textDatetime.text = sdf.format(memo.datetime)
        this.mMemo = memo
    }
}

 


 

init 블록 안에 있는 buttonDelete의 클릭리스너 블록 안에서 SQLite의 데이터를 먼저 삭제하고, listData의 데이터도 삭제 후 어댑터를 갱신. deleteMemo()는 null을 허용하지 않는데, mMemo는 null을 허용하도록 설정되었기 때문에 !!를 사용해서 강제해야 함.

init {
    binding.buttonDelete.setOnClickListener {
        helper?.deleteMemo(mMemo!!)
        listData.remove(mMemo)
        notifyDataSetChanged()
    }
}

 


 

에뮬레이터에서 실행하고 테스트 : 메모 데이터 하나를 삭제한 후 앱을 껐다 켰을 때도 삭제되어 있으면 정상적으로 동작하는 것

 

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


💡  간단한 메모를 저장하고 사용하는 프로젝트 

 

  이 프로젝트에서 메모 데이터를 저장하고 사용할 테이블을 다음처럼 설계

CREATE TABLE memo (
    num INTEGER PRIMARY KEY,
    content TEXT,
    datetime INTEGER
)

 

 -  SQLite 에서는 INTEGER 타입이 primary key 이면 숫자가 자동 증가됨


1.  SQLiteOpenHelper 사용하기

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

 

1) 프로젝트 생성 후 [app] - [java] 디렉토리 밑에 있는 패키지에 SQLiteHelper 클래스를 생성


2) 클래스 정의 및 생성자

 

-  SQLite 데이터베이스를 사용하려면 SQLiteOpenHelper 클래스를 상속받아야 함

SQLiteOpenHelper는 생성 시에 Context, 데이터베이스명, 팩토리, 버전 정보가 필요. 팩토리는 사용하지 않아도 되므로 나머지 세 가지 정보를 내가 만든 클래스의 생성자에 파라미터로 정의한 후에 상속받은 SQLiteOpenHelper에 전달.

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

 

3) SQLiteOpenHelper 메서드를 구현

class SQLiteHelper(context: Context, name: String, version: Int):
    SQLiteOpenHelper(context, name, null, version)  {
    override fun onCreate(p0: SQLiteDatabase?) {
        // 데이터베이스가 처음 생성될 때 호출
    }

    override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
        // 데이터베이스 업그레이드 시 호출
    }

 

4) 아직 데이터베이스가 생성되지 않았기 때문에 onCreate() 메서드에서 테이블을 생성

  -  이 메서드 안에 테이블 생성 쿼리를 작성하고 실행. 데이터베이스가 생성되어 있으면 더 이상 실행되지 않음. onCreate() 메서드 안에 앞에서 만든 테이블 생성 쿼리를 문자열로 입력한 후, db의 execSQL() 메서드에 전달해서 실행

override fun onCreate(p0: SQLiteDatabase?) {
    val sql = "CREATE TABLE `memo` (`num` INTEGER PRIMARY KEY," +
        "`content` TEXT, `datetime` INTEGER)"
        p0?.execSQL(sql)
}

 

5) Memo 데이터 클래스

 

  -  SQLiteHelper 클래스의 바깥에 Memo 클래스를 하나 생성하고 정의

        ➡️  이 클래스는 SQLite 데이터베이스와 상호작용하여 메모 데이터를 관리하는 기본적인 CRUD(생성, 읽기, 업데이트, 삭제) 작업을 수행할 수 있도록 함

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

 

num과 datetime의 타입을 데이터베이스에서는 INTEGER로 정의했는데, 여기서는 Long. 숫자의 범위가 서로 다르기 때문.
특별한 이유가 없다면 SQLite에서 INTEGER로 선언한 것은 소스 코드에서는 Long으로 사용.
그리고 num만 null을 허용한 것은 PRIMARY KEY 옵션으로 값이 자동으로 증가 되기 때문에 데이터 삽입 시에는 필요하지 않음.

 


2. 삽입 메서드

1) SQLiteOpenHelper를 이용해서 값을 입력할 때는 코틀린의 Map 클래스처럼 키, 값 형태로 사용되는 ContentValues 클래스를 사용

 

  👾  ContentValues에 put("컬럼명", 값)으로 저장

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

 

2) 상속받은 SQLiteOpenHelper에 이미 구현된 writableDatabase에 테이블명과 함께 앞에서 작성한 값을 전달해서 insert()하고, 사용한 후에는 close()를 호출하여 꼭 닫아줘야 함

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

    writableDatabase.insert("memo", null, value)
    writableDatabase.close()
}

 

  -  첫 번째 파라미터 : 테이블 이름 / 두 번째 파라미터 : 값이 null 일 때 입력 방지 / 세 번째 파라미터 : 삽입할 데이터를 포함하는 ContentValues 객체


3.  조회 메서드

1)  조회 메서드는 반환값이 있으므로 메서드의 가장 윗줄에 반환할 값을 변수로 선언하고, 가장 아랫줄에 반환하는 코드를 작성한 후 그 사이에 구현 코드를 작성하는 것이 좋음

fun selectMemo(): MutableList<Memo> {
    val list = mutableListOf<Memo>()
    
    return list
}

2) 메모의 전체 데이터를 조회하는 쿼리를 작성

val sql = "SELECT * FROM memo"

3) 읽기 전용 데이터베이스를 변수에 담음

val rd = readableDatabase

 

4) 데이터베이스의 rawQuery() 메서드에 앞에서 작성해둔 쿼리를 담아서 실행하면 커서 cursor 형태로 반환

 val cursor = rd.rawQuery(sql, null)

 

커서 Cursor


데이터셋을 처리할 때 현재 위치를 포함하는 데이터 요소. 커서를 사용하면 쿼리를 통해 반환된 데이터셋을 반복문으로 반복하며 하나씩 처
리할 수 있음. 반복할 때마다 커서가 현재 위치를 가르키고 있어 [데이터 읽기 -> 다음 줄 이동]의 단순 로직으로 데이터를 쉽게 처리할 수 있음.


 

5) 커서의 moveToNext() 메서드가 실행되면 다음 줄에 사용할 수있는 레코드가 있는지 여부를 반환하고, 해당 커서를 다음 위치로 이동. 레코드가 없으면 반복문을 빠져나감. 모든 레코드를 읽을 때까지 반복.

while(cursor.moveToNext()) { }

 

6) 반복문을 돌면서 테이블에 정의된 3개의 컬럼에서 값을 꺼낸 후 각각 변수에 담음

val num = cursor.getLong(0)
val content = cursor.getString(1)
val datetime = cursor.getLong(2)

 


 

7) 앞에서 변수에 저장해두었던 값들로 Memo 클래스를 생성하고 반환할 목록에 더함

list.add(Memo(num, content, datetime))

 

8) while 문의 블록 밖에서 커서와 읽기 전용 데이터베이스를 모두 닫아 줌

cursor.close()
rd.close()

    fun selectMemo(): MutableList<Memo> {
        val list = mutableListOf<Memo>()
        val sql = "SELECT * FROM memo"
        val rd = readableDatabase

        val cursor = rd.rawQuery(sql, null)
        while(cursor.moveToNext()) {
            val num = cursor.getLong(0)
            val content = cursor.getString(1)
            val datetime = cursor.getLong(2)
            list.add(Memo(num, content, datetime))
        }
        cursor.close()
        rd.close()
        return list
    }

 


4. 수정 메서드

1) INSERT와 동일하게 ContentValues를 사용해서 수정할 값을 저장

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

 

2) writableDatabase의 update() 메서드를 사용하여 수정한 다음 close()를 호출


update() 메서드의 파라미터는 총 4개인데 (테이블명, 수정할 값, 수정할 조건)
수정할 조건은 PRIMARY KEY로 지정된 컬럼을 사용하며 여기에서는 PRIMARY KEY인 컬럼이 num이기 때문에 'num = 숫자'가 됨.
세 번째 값을 'num = ?'의 형태로 입력하고, 네 번째 ?에 매핑할 값을 arrayOf("${memo.num}")의 형태로 전달할 수도 있음. 여기서는 세 번째에 조건과 값을 모두 할당했기 때문에 네 번째에 null을 사용

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

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

 


5. 삭제 메서드

SQLiteOpenHelper 클래스를 사용하면 insert(), update() 메서드의 사용법만 알면 쿼리를 몰라도 데이터베이스를 사용할 수 있음. 하지만 복잡한 데이터베이스를 다룰 때는 쿼리를 직접 작성하면 데이터를 더 정밀하게 다룰 수 있으므로 쿼리를 공부하는 것이 중요. 삭제 메서드 (DELETE)는 쿼리를 직접 입력해서 데이터를 삭제하는 코드로 작성.


1) 데이터 삭제 메서드를 작성

  -  조건식은 '컬럼명 = 값'의 형태가 됨. 삭제 쿼리를 작성하고 변수에 저장

 fun deleteMemo(memo: Memo) {
        val sql = "DELETE FROM memo WHERE num=${memo.num}"
 }

 

2) writableDatabase의 execSQL() 메서드로 쿼리를 실행한 후 close()를 호출. execSQL() 메서드로 쿼리를 직접 실행할 수 있음

fun deleteMemo(memo: Memo) {
        val sql = "DELETE FROM memo WHERE num=${memo.num}"
        val wd = writableDatabase
        wd.execSQL(sql)
        wd.close()
}

 

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  SQLite 프로그래밍

안드로이드에서 사용하는 SQLite는 관계형 데이터베이스

 

1) 안드로이드 앱 개발을 위한 SQLite 동작 방식

 

 

  🐰  안드로이드 앱에서 SQLite를 사용할 때는 일반적으로 SQLiteOpenHelper 클래스, SQLiteDatabase 클래스, Cursor 인터페이스를 이용

 

 

 

 

 

  • SQLiteOpenHelper 클래스를 상속받아 새로운 클래스를 생성
  • 생성한 클래스에는 데이터베이스 파일과 테이블을 생성하는 내용을 코딩 
  • SQLiteOpenHelper 클래스의 getWritableDatabase()를 사용하면 SQLiteDatabase를 반환받고execSQL() 또는 rawQuery() 등으로 SQL문을 실행 
  • 특히 SELECT문은 Cursor 인터페이스를 반환받은 후에 반복해서 테이블의 행 데이터에 접근

각 클래스에서 자주 사용되는 메서드
클래스 또는 인터페이스 메서드 주요 용도
SQLiteOpenHelper 클래스 생성자 DB 생성
  onCreate() 테이블 생성
  onUpgrade() 테이블 삭제 후 다시 생성
  getReadableDatabase() 읽기 전용 DB 열기. SQLiteDatabase 반환
  getWritableDatabase() 읽고 쓰기용 DB 열기. SQLiteDatabase 반환
SQLiteDatabase 클래스 execSQL() SQL문 (insert / update / delete) 실행
  close() DB 닫기
  query(), rawQuery() select 실행 후 커서 반환
Cursor 인터페이스 moveToFirst() 커서의 첫 행으로 이동
  moveToLast() 커서의 마지막 행으로 이동
  moveToNext() 현재 커서의 다음 행으로 이동

 


2.  가수 그룹의 이름과 인원을 데이터베이스에 입력하고 조회하는 응용 프로그램 작성

xml 코드

 

바깥 리니어 레이아웃 안에 리니어레이아웃(수평) 4개를 만들고 다음과 같이 화면 구성.
리니어 레이아웃 1 ~ 3은 layout_weight를 1로, 리니어 레이아웃 4는 8로 설정.

  • 리니어 레이아웃 1 : 텍스트뷰 1개, 에디트 텍스트 1개 (editName)
  • 리니어 레이아웃 2 : 텍스트뷰 1개, 에디트 텍스트 1개 (editNumber)
  • 리니어 레이아웃 3 : 버튼 3개 (btnInit, btnInsert, btnSelect)
  • 리니어 레이아웃 4 : 에디트 텍스트 2개 (editNameResult, editNumberResult)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/layout01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="이름"
            android:textSize="20dp"/>

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/editName" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/layout02"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="인원"
            android:textSize="20dp"/>

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/editNumber" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/layout03"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="horizontal"
        android:gravity="center_horizontal">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="10dp"
            android:text="초기화"
            android:id="@+id/btnInit" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="10dp"
            android:text="입력"
            android:id="@+id/btnInsert" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="조회"
            android:id="@+id/btnSelect" />


    </LinearLayout>

    <LinearLayout
        android:id="@+id/layout04"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_weight="8"
        android:gravity="center_vertical"
        android:background="#FBF4D1">

        <EditText
            android:id="@+id/editNameResult"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="1"/>

        <EditText
            android:id="@+id/editNumberResult"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="1"/>

    </LinearLayout>

</LinearLayout>

 


MainActivity
class MainActivity : AppCompatActivity() {
    lateinit var myHelper: MyDBHelper
    lateinit var editName: EditText
    lateinit var editNumber: EditText
    lateinit var editNameResult: EditText
    lateinit var editNumberResult: EditText
    lateinit var btnInit: Button
    lateinit var btnInsert: Button
    lateinit var btnSelect: Button
    lateinit var sqlDB: SQLiteDatabase

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setTitle("가수 그룹 관리 DB")

        editName = findViewById(R.id.editName)
        editNumber = findViewById(R.id.editNumber)
        editNameResult = findViewById(R.id.editNameResult)
        editNumberResult = findViewById(R.id.editNumberResult)

        btnInit = findViewById(R.id.btnInit)
        btnInsert = findViewById(R.id.btnInsert)
        btnSelect = findViewById(R.id.btnSelect)

        myHelper = MyDBHelper(this)

        btnInit.setOnClickListener {
            sqlDB = myHelper.writableDatabase
            myHelper.onUpgrade(sqlDB, 1, 2)
            sqlDB.close()
        }

        btnInsert.setOnClickListener {
            sqlDB = myHelper.writableDatabase
            sqlDB.execSQL("INSERT INTO `groupTBL` VALUES ('${editName.text.toString()}', " +
                    "${editNumber.text.toString()});")
            sqlDB.close()
            Toast.makeText(applicationContext, "입력됨", Toast.LENGTH_SHORT).show()
        }

        btnSelect.setOnClickListener {
            sqlDB = myHelper.writableDatabase
            val cursor: Cursor
            cursor = sqlDB.rawQuery("SELECT * FROM groupTBL;", null)

            var names = "그룹이름 \r\n ---------- \r\n"
            var numbers = "인원 \r\n ---------- \r\n"

            while(cursor.moveToNext()) {
                names += cursor.getString(0) + "\r\n"
                numbers += cursor.getString(1) + "\r\n"
            }

            editNameResult.setText(names)
            editNumberResult.setText(numbers)

            cursor.close()
            sqlDB.close()
        }
    }

    inner class MyDBHelper(context: Context): SQLiteOpenHelper(context, "groupDB", null, 1) {
        override fun onCreate(db: SQLiteDatabase?) {
            db!!.execSQL("CREATE TABLE `groupTBL`(`gName` CHAR(20) PRIMARY KEY, `gNumber` INTEGER);")
        }

        override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
            db!!.execSQL("DROP TABLE IF EXISTS `groupTBL`;")
            onCreate(db)
        }
    }
}

 

onCreate() 메서드 밖에 SQLiteOpenHelper 클래스에서 상속받는 내부 클래스를 정의한 후 생성자를 수정

 

inner class MyDBHelper(context: Context): SQLiteOpenHelper(context, "groupDB", null, 1) {}

 

super의 두 번째 파라미터에는 새로 생성될 데이터베이스의 파일명을 지정
마지막 파라미터는 데이터베이스 버전으로 처음에는 1을 지정


MyDBHelper 클래스의 onCreate()와 onUpgrade() 메서드를 코딩
override fun onCreate(db: SQLiteDatabase?) {
    db!!.execSQL("CREATE TABLE `groupTBL`(`gName` CHAR(20) PRIMARY KEY, 
        `gNumber` INTEGER);")

}


  테이블을 생성하는 SQL문.
  onUpgrade()에서 호출되거나 데이터를 입력할 때 혹은 테이블이 없을 때 처음 한 번 호출.

override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
    db!!.execSQL("DROP TABLE IF EXISTS `groupTBL`;")
    onCreate(db)
}

 

  테이블을 삭제하고 새로 생성. 초기화 할 때 호출

 


초기화 버튼를 클릭했을 때 동작하는 리스너 코딩
myHelper = MyDBHelper(this)


  생성자가 실행되어 groupDB 파일이 생성

sqlDB = myHelper.writableDatabase
myHelper.onUpgrade(sqlDB, 1, 2)
sqlDB.close()


  groupDB를 쓰기용 데이터베이스로 열고, onUpgrade() 메서드를 호출. groupTBL 테이블이 있으면 삭제한 후 새로 생성. 이후 데이터베이스를 닫음.
  onUpgrade()의 두번째 파라미터에는 이전 버전을, 세 번째 파라미터는 새버전을 입력하는데 파라미터를 받은 다음 사용하지 않았으므로 아무 숫자나 넣어도 됨.


입력 버튼를 클릭했을 때 동작하는 리스너 코딩

 

sqlDB = myHelper.writableDatabase


  groupDB를 쓰기용으로 열기

 

sqlDB.execSQL("INSERT INTO `groupTBL` VALUES ('${editName.text.toString()}'," +
    " ${editNumber.text.toString()});")

 

  insert 문을 생생 후 execSQL() 메서드로 실행


조 버튼를 클릭했을 때 동작하는 리스너 코딩
while (cursor.moveToNext()) {
    names += cursor.getString(0) + "\r\n"
    numbers += cursor.getString(1) + "\r\n"
}


  행 데이터의 개수만큼 반복. moveToNext() 메서드는 커서 변수의 다음 행으로 넘어감.
  만약 다음행이 없다면 false를 반환하여 while문이 끝남.
  getString(열번호)는 현재 커서의 열 번호 데이터 값을 반환하여 문자열 변수에 계속 누적.
  0은 0번째 열 (그룹이름 열), 1은 1번째 열(인원).

 

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]

+ Recent posts