오늘은 계산했던 기록을 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로 추가했다.
5. Room DAO
📌 최종 코드
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
}
}
'🍞 Front-End > 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 |