[Android + SQLite] 코틀린에서 SQLite를 사용해서 회원가입, 로그인 구현하기

LeeEunJae·2022년 8월 29일
2

Study Kotlin

목록 보기
11/20

레이아웃은 보이는바와 같으므로 xml 코드는 생략하겠습니다.

📌 User

유저정보를 관리하기 위한 객체입니다.
intent 로 User 객체가 담긴 ArrayList 를 전달해야해서 Serializable(직렬화) 인터페이스를 상속받았습니다.

import java.io.Serializable

class User(
    var id: String,
    var pw: String,
    var name: String
): Serializable {
    constructor(): this("","","")

}

📌 DBHelper.kt

SQLite 를 안드로이드에서 사용하기 위해 SQLiteOpenHelper을 상속받는 DBHelper 클래스를 생성합니다. 이 클래스에서 데이터베이스를 생성하고 관리할 수 있습니다.
저는 회원가입, 로그인, 유저정보 불러오기 등의 기능을 추가했습니다.


import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.provider.BaseColumns
import android.widget.Toast

class DBHelper(
    val context: Context?,
) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
    companion object{
        const val DATABASE_VERSION = 1
        const val DATABASE_NAME = "USER_AUTH"

        // Table
        const val TABLE_NAME = "USER"
        const val UID = "UID"
        const val COL_ID = "ID"
        const val COL_PW = "PW"
        const val COL_NAME = "NAME"
    }

    override fun onCreate(db: SQLiteDatabase) {
        // USER 라는 이름의 테이블을 생성하고 column 은 ID, PW, NAME 3개를 생성했습니다.
        // UID 는 SQLite 를 사용하기 위해서 필수적으로 필요한 column
        var sql: String = "CREATE TABLE IF NOT EXISTS " +
                "$TABLE_NAME ($UID integer primary key autoincrement, " +
                "$COL_ID text, $COL_PW text, $COL_NAME text);"
        db.execSQL(sql)
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        val sql = "DROP TABLE IF EXISTS $TABLE_NAME"

        db.execSQL(sql)
        onCreate(db)
    }

    // allUsers 리스트에 getter 를 만들어서 db 에 저장되어있는 모든 유저 정보를 가져옵니다.
    val allUsers:List<User>
        @SuppressLint("Range")
        get() {
            val users = ArrayList<User>()
            val selectQueryHandler = "SELECT * FROM $TABLE_NAME"
            val db = this.writableDatabase
            val cursor = db.rawQuery(selectQueryHandler,null)
            if(cursor.moveToFirst()){
                do{
                    val user = User()
                    user.id = cursor.getString(cursor.getColumnIndex(COL_ID))
                    user.pw = cursor.getString(cursor.getColumnIndex(COL_PW))
                    user.name = cursor.getString(cursor.getColumnIndex(COL_NAME))

                    users.add(user)
                }while(cursor.moveToNext())
            }
            db.close()
            return users
        }

    fun checkIdExist(id: String): Boolean{
        val db = this.readableDatabase

        // 리턴 받고자 하는 컬럼 값의 array
        val projection = arrayOf(UID)

        // where "id"=id and "password"=password 구문 적용하는 부분
        val selection= "$COL_ID = ?"
        val selectionArgs = arrayOf(id)

        // 정렬조건 지정
        val cursor = db.query(
            TABLE_NAME, // 테이블
            projection, // 리턴 받고자 하는 컬럼
            selection, // where 조건
            selectionArgs, // where 조건에 해당하는 값의 배열
            null, // 그룹 조건
            null, // having 조건
            null // orderby 조건 지정
        )
        // 반환된 cursor 값이 존재하면 아이디 중복(true), 존재하지 않으면 아이디 생성가능(false)
        return cursor.count > 0
    }

    // db 에 새로운 유저를 추가하는 메소드(회원가입)
    fun addUser(user: User){
        if(checkIdExist(user.id)) {
            Toast.makeText(this.context, "이미 존재하는 아이디 입니다.", Toast.LENGTH_SHORT).show()
            return
        }
        val db = this.writableDatabase
        val values = ContentValues()
        values.put(COL_ID, user.id)
        values.put(COL_PW, user.pw)
        values.put(COL_NAME, user.name)

        db.insert(TABLE_NAME, null, values)
        db.close()
        Toast.makeText(this.context,"회원가입 성공", Toast.LENGTH_SHORT).show()
    }
    // 로그인 메소드
    fun login(user: User) : Boolean{
        val db = this.readableDatabase

        val projection = arrayOf(UID)

        val selection = "$COL_ID = ? AND $COL_PW = ?"
        val selectionArgs = arrayOf(user.id, user.pw)

        val cursor = db.query(
            TABLE_NAME,
            projection,
            selection,
            selectionArgs,
            null,
            null,
            null
        )
        return cursor.count > 0
    }
    // 유저 정보 업데이트 메소드
    fun updateUser(user: User): Int{
        val db = this.writableDatabase
        val values = ContentValues()
        values.put(COL_ID, user.id)
        values.put(COL_PW, user.pw)
        values.put(COL_NAME, user.name)

        return db.update(TABLE_NAME, values, "$COL_ID=?", arrayOf(user.id))
    }

    // 유저 삭제 메소드
    fun deleteUser(user: User){
        val db = this.writableDatabase

        db.delete(TABLE_NAME, "$COL_ID=?", arrayOf(user.id))
        db.close()
    }
}

📌 MainActivity.kt

메인 액티비티 입니다.


import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import com.dldmswo1209.sql_practice.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    lateinit var db: DBHelper
    var users = ArrayList<User>()
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        db = DBHelper(this)

        binding.registerButton.setOnClickListener {
            createUser().let {
                if (it != null) {
                    db.addUser(it)
                }
            }
        }
        binding.deleteButton.setOnClickListener {
            createUser().let {
                if (it != null) {
                    db.deleteUser(it)
                }
            }
        }
        binding.selectButton.setOnClickListener {
            users.clear() // 리스트 초기화 -> 초기화를 하지 않으면 데이터가 중복되서 쌓이게 된다.
            db.allUsers.forEach { // db에 저장되어 있는 유저 정보들을 가져와서 리스트에 저장
                users.add(it)
            }
            val intent = Intent(this, SelectActivity::class.java) // 인텐트 객체 생성
            // ArrayList 객체를 인텐트로 전달하려면 ArrayList 에 담기는 데이터 클래스가 Serializable(직렬화) 이 되어 있어야 함.
            intent.putExtra("users", users) // 인텐트에 데이터 전달
            startActivity(intent)

        }
        binding.loginButton.setOnClickListener {
            createUser().let {
                if (it != null) {
                    if (db.login(it)) {
                        Toast.makeText(this, "로그인 성공!", Toast.LENGTH_SHORT).show()
                        val intent = Intent(this, LoginSuccessActivity::class.java)
                        intent.putExtra("name", binding.nameEditText.text.toString())
                        startActivity(intent)
                    } else {
                        Toast.makeText(this, "로그인 실패!", Toast.LENGTH_SHORT).show()
                    }
                }else{
                    Toast.makeText(this, "정보를 모두 입력해주세요", Toast.LENGTH_SHORT).show()
                }

            }
        }

    }
    private fun createUser(): User?{
        val id = binding.idEditText.text.toString()
        val pw = binding.pwEditText.text.toString()
        val name = binding.nameEditText.text.toString()
        if(id == "" || pw == "" || name =="") // 입력 정보가 하나라도 비어있으면
            return null // Null 반환

        return User(id,pw,name)
    }


}

📌 UserListAdapter.kt

메인화면에서 select 버튼을 클릭하면 현재 db에 저장되어있는 유저 정보들을 가져와서 RecyclerView 에 리스트 형태로 띄워주도록 구현했습니다. 따라서 다음의 어답터가 필요합니다. item에 아이디와 이름만 표시하도록 구현했습니다.

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.dldmswo1209.sql_practice.databinding.UserItemBinding

class UserListAdapter: ListAdapter<User, UserListAdapter.ViewHolder>(diffUtil) {
    inner class ViewHolder(private val binding: UserItemBinding): RecyclerView.ViewHolder(binding.root){
        fun bind(user: User){
            binding.idTextView.text = user.id
            binding.nameTextView.text = user.name
        }

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(UserItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(currentList[position])
    }

    companion object{
        val diffUtil = object: DiffUtil.ItemCallback<User>(){
            override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem.id == newItem.id
            }

            override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem == newItem
            }
        }
    }
}

📌 SelectActivity.kt

메인 화면에서 select 버튼 클릭시 해당 액티비티로 이동하게 됩니다.
위에서 만든 어답터를 생성(UserListAdapter())하고 메인 액티비티에서 전달한 ArrayList(db에 있는 모든 유저 정보가 담긴 리스트)를 가져와서 리스트를 전달(submitList)해줍니다


import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.dldmswo1209.sql_practice.databinding.ActivitySelectBinding

class SelectActivity : AppCompatActivity() {
    private lateinit var binding: ActivitySelectBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivitySelectBinding.inflate(layoutInflater)
        setContentView(binding.root)
        val intent = intent
        // ArrayList 객체를 가져옴
        val users = intent.getSerializableExtra("users") as ArrayList<User>

        val userListAdapter = UserListAdapter()
        userListAdapter.submitList(users)
        binding.recyclerView.adapter = userListAdapter

    }
}

📌 LoginSuccessActivity.kt

메인화면에서 Login 버튼을 누르고 로그인 성공시 해당 액티비티로 이동하게 됩니다. 메인 액티비티에서 로그인 한 유저의 이름을 전달 받고, 화면에 띄워줍니다.

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.dldmswo1209.sql_practice.databinding.ActivityLoginSuccessBinding

class LoginSuccessActivity : AppCompatActivity() {
    private lateinit var binding: ActivityLoginSuccessBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityLoginSuccessBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val intent = intent
        val name = intent.getStringExtra("name")

        binding.textView.text = "안녕하세요 ${name}님"
    }
}

👀 참고자료

https://fflask.tistory.com/36

📌 전체코드 보러가기

https://github.com/EJLee1209/SQLiteWithAndroid

📌 느낀점

SQLite 는 유저정보를 관리하기에 적절하지 않은 것 같다. 얼핏보기에는 정상적으로 동작하는 것처럼 보이지만, 앱을 삭제하면 데이터베이스도 같이 삭제된다. 즉, 핸드폰에 저장되는 데이터 베이스인 것이다.
유저 정보를 관리하고, 채팅과 같은 기능을 구현하려면 결국 mysql을 사용해야 한다. 하지만, mysql 은 안드로이드와 보안문제 때문에 직접 연결이 불가하기 때문에 서버가 필요하다.
공부를 하면 할수록 알아야 하는 지식이 무수히 많음을 새삼 느낀다😅

+) 채팅과 같은 기능을 구현할 때, 주고받은 메세지들은 SQLite 에 저장하면 될 것 같다. 매번 서버에서 주고받은 메시지들을 가져온다면 서버 부하가 클 것이라고 생각된다. 우리가 항상 사용하는 카톡도 지웠다가 다시 설치하면 메시지가 지워지는 것을 생각해봤을 때 카톡도 SQLite에 저장하는 것이라고 추측된다.

profile
매일 조금씩이라도 성장하자

0개의 댓글