[Android] 계산기 RoomDB

2022. 10. 31. 17:34·🍞 FrontEnd/Android

오늘은 계산했던 기록을 DB에 저장하는 계산기를 만들어볼 것이다.

단, 정수형으로 한정하고 몇 가지 기능을 배제하고 연산자는 1회만 사용할 수 있도록 할 것이다!

 

Thread 사용하기

- 타 Thread 만들어서 사용하기

- runOnUiThread 사용하기

Room 사용하기

 

📌 알게 된 점

1. TableLayout 속성에 shrinkColumns="*"을 주면 열 간격이 균일하게 적용된다.

2. drawable 누를 때 색상 변하게 하는 것은 ripple

3. Button에 바로 background를 하면 적용되지 않기 때문에 AppCompatButton으로 변경해주거나, stateListAnimator="null"로 설정해줘야 한다.

4. DB table로 사용해주기 위해 @Entity를 추가해준다.

 

@Entity를 import 하기 위해선 Room 라이브러리를 추가해야 한다.

일단, Gradle 파일의 plugins에 id 'kotlin-kapt'를 추가해주고 dependencies엔 아래 코드를 추가해준다.

 

각각의 변수들을 어떠한 이름으로 DB에 저장되는지 명시를 해줘야한다.

그래서 uid는 @PrimaryKey,  expression과 result는 @ColumnInfo로 추가했다.

History.kt (DB table)

5. Room DAO

 

Room DAO를 사용하여 데이터 액세스  |  Android 개발자  |  Android Developers

Room 라이브러리의 일부인 DAO(데이터 액세스 개체)를 사용하여 데이터베이스 테이블을 수정하는 방법 알아보기

developer.android.com

 

📌 최종 코드

MainActivity.kt

package com.example.calculator

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.room.Room
import com.example.calculator.databinding.ActivityMainBinding
import com.example.calculator.model.History

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private var isOperator = false
    private var hasOperator = false

    lateinit var db: AppDatabase

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

        db = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java,
            "historyDB"
        ).build()
    }

    fun buttonClicked(v: View) {
        when (v.id) {
            R.id.button0 -> numberButtonClicked("0")
            R.id.button1 -> numberButtonClicked("1")
            R.id.button2 -> numberButtonClicked("2")
            R.id.button3 -> numberButtonClicked("3")
            R.id.button4 -> numberButtonClicked("4")
            R.id.button5 -> numberButtonClicked("5")
            R.id.button6 -> numberButtonClicked("6")
            R.id.button7 -> numberButtonClicked("7")
            R.id.button8 -> numberButtonClicked("8")
            R.id.button9 -> numberButtonClicked("9")

            R.id.buttonPlus -> operatorButtonClicked("+")
            R.id.buttonMinus -> operatorButtonClicked("-")
            R.id.buttonMulti -> operatorButtonClicked("*")
            R.id.buttonDivider -> operatorButtonClicked("/")
            R.id.buttonModulo -> operatorButtonClicked("%")
        }
    }

    private fun numberButtonClicked(number: String) {
        if (isOperator) {
            binding.expressionTextView.append(" ")
        }
        isOperator = false

        val expressionText = binding.expressionTextView.text.split(" ")
        if (expressionText.isNotEmpty() && expressionText.last().length >= 15) {
            Toast.makeText(this, "15자리까지만 사용할 수 있습니다", Toast.LENGTH_SHORT).show()
            return
        } else if (expressionText.last().isEmpty() && number == "0") {
            Toast.makeText(this, "0은 제일 앞에 올 수 없습니다", Toast.LENGTH_SHORT).show()
            return
        }
        binding.expressionTextView.append(number)
        binding.resultTextView.text = calculateExpression()
    }

    private fun operatorButtonClicked(operator: String) {
        if (binding.expressionTextView.text.isEmpty()) { // 연산자가 첫 자리
            return
        }
        when {
            isOperator -> {
                val text = binding.expressionTextView.text.toString()
                binding.expressionTextView.text = text.dropLast(1) + operator
            }
            hasOperator -> {
                Toast.makeText(this, "연산자는 한 번만 사용할 수 있습니다.", Toast.LENGTH_SHORT).show()
                return
            }
            else -> {
                binding.expressionTextView.append(" $operator")
            }
        }

        val ssb = SpannableStringBuilder(binding.expressionTextView.text)
        ssb.setSpan(
            ForegroundColorSpan(ContextCompat.getColor(this, R.color.green)),
            binding.expressionTextView.text.length - 1,
            binding.expressionTextView.text.length,
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
        )
        binding.expressionTextView.text = ssb

        isOperator = true
        hasOperator = true
    }

    fun resultButtonClicked(v: View) {
        val expressionTexts = binding.expressionTextView.text.split(" ")

        if (binding.expressionTextView.text.isEmpty() || expressionTexts.size == 1) {
            return
        }
        if(expressionTexts.size != 3 && hasOperator) {
            Toast.makeText(this, "아직 완성되지 않은 수식입니다.", Toast.LENGTH_SHORT).show()
            return
        }
        if (expressionTexts[0].isNumber().not() || expressionTexts[2].isNumber().not()) {
            Toast.makeText(this, "오류가 발생했습니다.", Toast.LENGTH_SHORT).show()
            return
        }

        val expressionText = binding.expressionTextView.text.toString()
        val resultText = calculateExpression()

        // TODO 디비에 넣어주는 부분
        Thread(Runnable {
            db.historyDao().insertHistory(History(null, expressionText, resultText))
        }).start()

        binding.resultTextView.text = ""
        binding.expressionTextView.text = resultText

        isOperator = false
        hasOperator = false
    }

    private fun calculateExpression(): String {
        val expressionTexts = binding.expressionTextView.text.split(" ")

        if (hasOperator.not() || expressionTexts.size != 3) {
            return ""
        } else if (expressionTexts[0].isNumber().not() || expressionTexts[2].isNumber().not()) {
            return ""
        }

        val exp1 = expressionTexts[0].toBigInteger()
        val exp2 = expressionTexts[2].toBigInteger()
        val op = expressionTexts[1]

        return when (op) {
            "+" -> (exp1 + exp2).toString()
            "-" -> (exp1 - exp2).toString()
            "*" -> (exp1 * exp2).toString()
            "/" -> (exp1 / exp2).toString()
            "%" -> (exp1 % exp2).toString()
            else -> ""
        }
    }

    fun clearButtonClicked(v: View) {
        binding.expressionTextView.text = ""
        binding.resultTextView.text = ""
        isOperator = false
        hasOperator = false
    }

    fun historyButtonClicked(v: View) {
        binding.historyLayout.isVisible = true
        binding.historyLinearLayout.removeAllViews()

        // 디비에서 모든 기록 가져오기
        // 뷰에서 모든 기록 할당하기

        Thread(Runnable {
            db.historyDao().getAll().reversed().forEach {
                runOnUiThread {
                    val historyView = LayoutInflater.from(this).inflate(R.layout.history_row, null, false)
                    historyView.findViewById<TextView>(R.id.expressionTextView).text = it.expression
                    historyView.findViewById<TextView>(R.id.resultTextView).text = "= ${it.result}"

                    binding.historyLinearLayout.addView(historyView)
                }
            }
        }).start()
    }

    fun closeHistoryButtonClicked(v: View) {
        binding.historyLayout.isVisible = false
    }

    fun historyClearButtonClicked(v: View) {
        binding.historyLinearLayout.removeAllViews() // 뷰에서 모든 기록 삭제

        Thread(Runnable { // 디비에서 모든 기록 삭제
            db.historyDao().deleteAll()
        }).start()
    }
}

// 객체 확장 함수
fun String.isNumber(): Boolean {
    return try {
        this.toBigInteger()
        true
    } catch (e: NumberFormatException) {
        false
    }
}

 

HistoryDao.kt
History.kt (DB table)
AppDatabase (DB)

 

GitHub - uiop5809/Android-Projects: 안드로이드 프로젝트 with Kotlin

안드로이드 프로젝트 with Kotlin. Contribute to uiop5809/Android-Projects development by creating an account on GitHub.

github.com

 

저작자표시 (새창열림)

'🍞 FrontEnd > Android' 카테고리의 다른 글

[Android] Progress 타이머  (0) 2022.11.08
[Android] 앱 권한 요청 API level 오류  (0) 2022.11.02
[Android] 에뮬레이터에서 Toast 메세지가 안 보일 때  (0) 2022.10.31
[Android] 비밀 다이어리  (0) 2022.10.28
[Android] 로또 번호 추첨기  (2) 2022.10.27
'🍞 FrontEnd/Android' 카테고리의 다른 글
  • [Android] Progress 타이머
  • [Android] 앱 권한 요청 API level 오류
  • [Android] 에뮬레이터에서 Toast 메세지가 안 보일 때
  • [Android] 비밀 다이어리
박빵이
박빵이
2025년에도 갓생살기
  • 박빵이
    기억보다 기록
    박빵이
  • 전체
    오늘
    어제
    • 분류 전체보기 (337)
      • 🍞 FrontEnd (97)
        • HTML+CSS (4)
        • JavaScript (17)
        • TypeScript (4)
        • React (52)
        • Next.js (2)
        • Android (15)
      • 🍞 BackEnd (24)
        • Java (15)
        • Node.js (6)
        • Spring (1)
      • 🍞 Cloud & Infra (0)
        • AWS SAA (0)
        • Microsoft Azure (0)
      • 🍞 Algorithm (147)
        • C++ (4)
        • Baekjoon (41)
        • Programmers (97)
      • 🍞 Computer Science (18)
        • 운영체제 (1)
        • 데이터 통신 (6)
        • 네트워크 (6)
        • 데이터베이스 (1)
      • 🍞 대외활동 & 부트캠프 (42)
        • 삼성 청년 SW 아카데미 (1)
        • LG유플러스 유레카 (0)
        • 한국대학생IT경영학회 (1)
        • IT연합동아리 UMC (17)
        • 길벗 블로깅 멘토 (18)
        • IT연합동아리 피로그래밍 (3)
        • 개발 컨퍼런스 (2)
  • 블로그 메뉴

    • Admin
  • 링크

    • GitHub
  • 인기 글

  • 태그

    umc
    level1
    길벗 블로깅 멘토링
    Front
    JavaScript
    백준
    위상정렬
    level2
    안드로이드
    코틀린
    map
    코딩자율학습
    Android
    Java
    길벗 블로깅 멘토
    react
    알고리즘
    유니온파인드
    C++
    프로그래머스
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
박빵이
[Android] 계산기 RoomDB
상단으로

티스토리툴바