국비교육(22-23)

76일차(2)/Android App(34) : SQLite 활용(2)

서리/Seori 2023. 1. 26. 21:57

76일차(3)/Android App(34) : SQLite 활용(2)

 

- TodoDao에 할일 수정, 삭제 메소드 추가하기

- SQLite 날짜, 요일 설정방법

 

TodoDao

package com.example.step14sqlite

import android.database.Cursor

/*
    TodoDao 클래스의 대표생성자(primary constructor) 의 인자로 DBHelper 객체를 전달받아서
    필드에 넣어두고 메소드에서 활용하는 구조이다.

    insert, update, delete 작업을 할때는
        val db:SQLiteDataBase = helper.getWritableDataBase()
        val db:SQLiteDataBase = helper.writableDataBase()
    select 작업을 할때는
       val db:SQLiteDataBase = helper.readableDataBase()

    java의 JDBC에서 PreparedStatement 객체와 비슷한 기능을 하는 객체가 SQLiterDataBase 객체이다.
 */
class TodoDao(var helper:DBHelper) {

    //할일 정보를 삭제하는 메소드
    fun delete(num:Int){
        val db=helper.writableDatabase
        val args = arrayOf<Any>(num)
        val sql = "DELETE FROM todo WHERE num=?"
        db.execSQL(sql, args)
        db.close()
    }
    //할일 정보 하나를 수정하는 메소드
    fun update(data: Todo){
        /*
        val db=helper.writableDatabase
        val args= arrayOf<Any>(data.content, data.num)
        val sql="UPDATE todo SET content=? WHERE num=?"
        db.execSQL(sql, args)
        db.close()
         */
        with(helper.writableDatabase){
            execSQL("UPDATE todo SET content=? WHERE num=?", arrayOf<Any>(data.content, data.num))
            close()
        }
    }


    //할일 정보를 저장하는 메소드
    fun insert(data:Todo){
        //java => SQLiteDataBase db=helper.getWritableDataBase()
        val db=helper.writableDatabase
        // ? 에 바인딩할 인자를 Any 배열로 얻어내기
        //java => Object[] args = { data.getContent() }
        var args= arrayOf<Any>(data.content)
        //실행할 sql문
        // SQLiteDB => datetime('now', 'localtime'), oracle => SYSDATE
        val sql = "INSERT INTO todo (content, regdate)" +
                " VALUES(?, datetime('now', 'localtime'))"
        //sql문 실행하기
        db.execSQL(sql, args)
        db.close() //close() 를 호출해야 실제로 반영된다.

    }
    //할일 목록을 리턴하는 함수
    fun getList():List<Todo>{
        //수정가능한 todo type을 담을 수 있는 리스트
        val list= mutableListOf<Todo>()
        val db=helper.readableDatabase

        /*
           SQLite DB 에서 날짜 format 만들기

           - strftime() 함수를 활용한다.
           year : %Y
           month : %m
           date : %d
           week of day 0 1 2 3 4 5 6 : %w
           hour : %H
           minute : %M

           substr('일월화수목금토', strftime('%w', regdate)+1, 1)
           substr('일요일월요일화요일수요일목요일금요일토요일', strftime('%w', regdate)*3+1, 3)
         */

        //실행할 select 문 구성 (binding 할것은 없음)
        val sql="SELECT num, content, " +
                " strftime('%Y년 %m월 %d일 ', regdate) " +
                "|| substr('일월화수목금토', strftime('%w', regdate)+1, 1)" +
                "|| strftime(' %H:%M', regdate)" +
                " FROM todo ORDER BY num ASC"
        //query 문을 수행하고 결과를 Cursor 객체로부터 얻어내기 (selection 인자는 없다.)
        val result:Cursor = db.rawQuery(sql, null)

        //Cursor 객체의 메소드를 이용해서 담긴 내용을 반복문 돌면서 호출한다.
        while (result.moveToNext()){
            //0번째는 num, 1번째는 content, 2번째는 regdate이다.
            val data=Todo(result.getInt(0), result.getString(1), result.getString(2))
            //할일 정보가 담긴 Todo 객체를 List에 추가한다.
            list.add(data)
        }
        //할일 목록을 리턴해주기
        return list
    }
}

 

- 마지막에 close() 까지 호출해야만 반영된다.

 

- sql문, array 정보를 받아서 데이터를 삭제한다.

- args 라는 상수를 굳이 만들 필요는 없다. 바로 execSQL 에 인자로 넣어주어도 된다.

 

- sql문에서 바인딩할 ? 의 순서에 유의해서 작성하기

 

** 조금 더 코틀린스러운 코드로 바꾸어본다면?

 

 

- 불러다 놓고, 객체를 여러번 사용할 것이라면 with { } 안에서 쓰기

- 이 객체의 메소드를 연속으로 2번 호출하므로, 이렇게 작성하면 코드가 간단해진다.

 


 

- 날짜 문자열 출력방식 수정

 

- datetime('now','localtime') 으로 작성했을 때의 기본 형식

- TO_CHAR() 와 같은 방식으로 날짜형식을 바꿀수 있을까?

 

[ SQLite DB 에서 날짜 format 만들기 ]
- strftime() 함수를 활용한다.
- year : %Y
- month : %m
- date : %d
- week of day 0 1 2 3 4 5 6 : %w
- hour : %H
- minute : %M

- strftime() 사용

 

- 일요일 0, 월요일 1, ... , 토요일 6 순서로 숫자가 부여되어있다.

- 요일은 숫자로 지정되어 있다. 숫자에서 필요한 문자를 빼낸다고 생각하기

 

ex)

substr('일월화수목금토', strftime('%w', regdate)+1, 1)

substr('일요일월요일화요일수요일목요일금요일토요일', strftime('%w', regdate)*3+1, 3)

 

- substr() 함수는 이런 형태로 쓴다.

- 몇번째 인덱스에서 몇개의 문자를 가지고 올 것인지 표기!

 

- 이 숫자 중 하나가 이 위치에 들어온다. 이 문자열의 index 번호 +1번째로부터 1개만 가져온다는 뜻!

- 일요일이라고 하면 일월화수목금토에서 1번째 숫자를 1개 빼오는 것으로 작성하면 된다. 

 

- 이렇게 날짜형식이 바뀐것을 볼 수 있다.

 

- 요일까지 넣으려면 파이프문자 두개 | | 로 연결하기 

- 연월일, 요일, 시분초를 따로 작성한다.

 

 

- 이렇게 나타나게 할 수 있다.

- 문자열3개를 데이터베이스 내에서 단순 연결한 것이다.

 


 

- 아이템을 추가하면 자동으로 아래쪽으로 스크롤되어 가장 최근에 추가한 것을 보고 싶다면?

 

MainActivity

package com.example.step14sqlite

import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import android.widget.*
import android.widget.AdapterView.OnItemLongClickListener
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() , View.OnClickListener, OnItemLongClickListener {

    //필요한 필드 정의하기

    //lateinit 예약어를 사용하면 null을 넣을 필요없이 값을 나중에 넣을 수 있다.
    //null을 대입했다가 나중에 값을 바꾸려면 번거롭다.
    lateinit var inputText:EditText
    lateinit var listView: ListView
    lateinit var adapter: TodoAdapter
    lateinit var helper: DBHelper

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

        //필요한 UI의 참조값 얻어와서 필드에 저장하기
        inputText=findViewById(R.id.inputText)
        listView=findViewById(R.id.listView)
        //버튼 리스너 등록하기
        findViewById<Button>(R.id.addBtn).setOnClickListener(this)
        //DBhelper 객체의 참조값을 필드에 저장하기
        //version값이 증가되면 onUpgrade() 메소드가 자동 호출되면서 db가 초기화된다.
        helper = DBHelper(this, "MyDB.sqlite", null, 2)
        //할일 목록 얻어오기
        var list:List<Todo> = TodoDao(helper).getList()
        //listView 동작 준비하고, 할일 목록 출력하기
        adapter=TodoAdapter(this, R.layout.listview_cell, list)
        //listView에 아답타 연결하기
        listView.adapter=adapter
        //listView에 LongClickListener 등록하기
        listView.setOnItemLongClickListener(this)
    }

    override fun onClick(v: View?) {
        //1. 입력한 문자열을 읽어와서
        val msg=inputText.text.toString()
        //2. Todo 객체에 담아서
        val data=Todo(0, msg, "")
        //3. TodoDao객체를 이용해서 DB에 저장한다.
        TodoDao(helper).insert(data)
        //4. 목록을 새로 얻어와서
        val list=TodoDao(helper).getList()
        //5. 아답타에 넣어주고
        adapter.list=list
        //6. 모델의 내용이 바뀌었다고 아답타에 알려서 ListView가 업데이트되도록 한다.
        adapter.notifyDataSetChanged()
        //7. Toast 띄우기
        Toast.makeText(this, "저장했습니다.", Toast.LENGTH_SHORT).show()
        inputText.setText("")
        //8. listView의 가장 아래쪽이 화면에 보일 수 있도록 부드럽게 스크롤시키기
        listView.smoothScrollToPosition(adapter.count)

    }
    //listView의 cell을 오랫동안 클릭하고 있으면 호출되는 메소드
    override fun onItemLongClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long): Boolean {
        //여기서 position은 클릭한 cell의 인덱스
        //여기서 id는 클릭한 cell의 아이디( Todo의 Primary key )
        //id에 전달되는 값은 TodoAdapter의 getItemId() 메소드에서 리턴한 값

        AlertDialog.Builder(this)
                .setTitle("알림")
                .setMessage("삭제하시겠습니까?")
                .setPositiveButton("네", DialogInterface.OnClickListener {
                    dialog, which ->
                        val dao=TodoDao(helper)
                        dao.delete(id.toInt())
                        //목록을 새로 얻어와서
                        val list=TodoDao(helper).getList()
                        //아답타에 넣어주고
                        adapter.list=list
                        //모델의 내용이 바뀌었다고 아답타에 올려서 listView가 업데이트되도록 한다.
                        adapter.notifyDataSetChanged()
                })
                .setNegativeButton("아니오", null)
                .create()
                .show()

        return false
    }
}

 

listView.smoothScrollToPosition(adapter.count)

 

- smoothScrollToPosition() 이라는 기능이 있다. 아래쪽으로 스크롤해주는 기능!

- adapter.count 는? 모델의 개수(list.size)를 넣어주면 된다.

 

 


 

- 오래 클릭했을 때(LongClickListener), 알림창을 띄워서 삭제의사를 물어보는 기능 추가

 

- OnItemLongClickListener 인터페이스를 구현하고 메소드 오버라이드

 

- AlertDialog.Builder 로 알림창 상세 설정하기

 

- setPositiveButton 에 리스너 등록하기

- 메소드 하나짜리면 이렇게 { } 으로 전달할 수 있다!

 

- 이렇게 ctrl+space 로 작성할 onClickListener의 형태를 자동완성시킬 수 있다.

 

- 여기서 which는 어떤 인덱스를 클릭했는지 나오는것이다.

 

 AlertDialog.Builder(this)
        .setTitle("알림")
        .setMessage("삭제하시겠습니까?")
        .setPositiveButton("네", DialogInterface.OnClickListener {
            dialog, which ->
                val dao=TodoDao(helper)
                dao.delete(id.toInt())
                //목록을 새로 얻어와서
                val list=TodoDao(helper).getList()
                //아답타에 넣어주고
                adapter.list=list
                //모델의 내용이 바뀌었다고 아답타에 올려서 listView가 업데이트되도록 한다.
                adapter.notifyDataSetChanged()
        })
        .setNegativeButton("아니오", null)
        .create()
        .show()
 return false

- helper를 사용해서 TodoDao객체를 생성하고, delete() 메소드 실행

- 메소드의 id는 아답타에서 리턴한 값

- num을 받아와서 삭제해주고, getlist() 로 새로 리스트를 가져와서 아답타에 넣어 출력해준다.

 

- 이렇게 창이 뜨고 네를 선택하면 삭제되면서 리스트가 새로고침된다.

 

 

- spring boot-android 연동 예정

- 웹서버 연결 예정