Side Projects - Calculator
우리가 평소에 기본 앱으로 자주 사용하는 계산기 앱을 구현하는 간단한 프로젝트
기본적인 사칙연산 기능 뿐만 아니라 계산 히스토리를 볼 수 있는 기능을 추가한 앱을 개발합니다.
- GitHub Repository-> https://github.com/hwayeon351/Calculator
학습 회고
오늘은 계산기 앱 마지막 실습을 했다.
이전에 계산한 계산식과 결과에 관한 히스토리를 보여주는 기능을 개발했다.
1. = 버튼(resultButton)을 클릭해 계산 결과를 구할 때마다 계산식과 결과를 데이터베이스에 저장한다.
2. 히스토리 버튼(historyButton)을 클릭하면 그동안 계산했던 계산식과 결과를 데이터베이스에서 불러와 보여준다.
3. 계산기록 삭제 버튼(historyClearButton)을 클릭하면 모든 히스토리 기록을 데이터베이스에서 삭제하고 빈 결화를 화면에 띄운다.
이를 위해 앱의 내부저장소에 DB를 만들어서 저장해줬다. Room 라이브러리를 사용하였다.
오늘 공부한 내용
- Room 지속성 라이브러리를 사용하여 로컬 데이터베이스 구축하기
Room은 SQLite에 대한 추상화 레이어를 제공해서 원활한 데이터베이스 액세스를 지원한다.
Room에는 데이터베이스, Entities, DAO(Data Access Objects)라는 3가지 구성요소가 있다.
앱은 Room 데이터베이스를 사용해서 데이터베이스와 연결된 데이터 액세스 개체(DAO)를 가져온다. 그 다음 앱은 각 DAO를 사용해서 데이터베이스에서 Entity를 가져오고 Entity의 변경사항을 다시 데이터베이스에 저장한다.
앱은 Entities를 사용하여 데이터베이스 내의 테이블의 열에 해당하는 값을 가져오고 설정한다.
1. 데이터베이스
데이터베이스 홀더를 포함하며 앱의 기본 액세스 포인트 역할을 하여 지속적인 관계형 데이터의 기본 연결을 도와준다.
- @Database 어노테이션을 지정하며 RoomDatabase를 확장하는 추상클래스이어야 한다.
- 어노테이션 내에 데이터베이스와 연결된 Entities의 목록을 포함해야 한다.
- 매개변수가 0개이며 @DAO로 어노테이션이 지정된 클래스를 반환하는 추상 메서드를 포함해야 한다.
이렇게 선언한 데이터베이스를 런타임 시에 Room.databaseBuilder()를 호출해서 database 인스턴스를 가져올 수 있다.
2. Entities
데이터베이스 내의 테이블을 나타낸다.
각 Entity는 하나 이상의 필드를 기본 키로 정의해야한다. @PrimaryKey 어노테이션을 사용해서 정의할 수 있다.
복합키를 정의할 때는 @Entity 어노테이션의 primaryKeys 속성을 활용할 수 있다.
@Entity(primaryKeys = arrayOf("firstName", "lastName"))
data class User(
val firstName: String?,
val lastName: String?
)
기본적으로 Room은 데이터 클래스 이름을 데이터베이스 테이블 이름으로 사용한다.
만약, 테이블 이름을 다르게 지정하고 싶으면 @Entity 어노테이션의 tableName 속성을 설정하면 된다.
@Entity(tableName = "users")
data class User (
// ...
)
Room은 필드 이름을 데이터베이스의 열 이름으로 사용한다. 열이름을 다르게 지정하려면 @ColumnInfo 어노테이션을 필드에 추가하면 된다.
@Entity(tableName = "users")
data class User (
@PrimaryKey val id: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
3. DAO(Data Access Objects)
데이터베이스에 액세스하는 데 사용되는 메서드가 포함되어 있다.
- 삽입 Insert
@Insert 어노테이션을 달아 단일 트랜잭션으로 모든 매개변수를 데이터베이스에 삽입하는 매서드를 구현할 수 있다.
@Dao
interface HistoryDao {
@Insert
fun insertHistory(history: History)
}
- 갱신 Update
@Update 어노테이션을 달아 데이터베이스 수정이 가능하다.
@Dao
interface MyDao {
@Update
fun updateUsers(vararg users: User)
}
- 삭제 Delete
@Delete 어노테이션을 달아 데이터베이스에서 삭제가 가능하다.
@Dao
interface HistoryDao {
@Delete
fun delete(history: History)
}
- 쿼리 Query
@Query 어노테이션을 달아서 데이터베이스에서 읽기/쓰기 작업을 수행할 수 있다.
@Dao
interface HistoryDao {
@Query("SELECT * FROM history")
fun getAll(): List<History>
// :변수명을 사용하여 쿼리에 매개변수 전달
@Query("SELECT * FROM history WHERE result LIKE :result LIMIT 1")
fun findByResult(result: String): History
@Query("SELECT * FROM history WHERE result LIKE :result")
fun findByResults(result: String): List<History>
}
https://developer.android.com/training/data-storage/room?hl=ko#kts
- LayoutInflater
히스토리 버튼을 클릭하면 기존 계산기의 버튼이 차지한 뷰 위에 히스토리 뷰가 나타난다.
히스토리 뷰에 사용자가 계산식과 결과를 발생시킬 때마다 데이터베이스에 저장하고 저장된 모든 계산식과 결과를 표시해줘야 한다.
이 경우, LayoutInflater를 이용해서 계산식과 결과가 생길 때마다 TextView를 생성해서 HistoryLinearLayout에 추가해 줄 수 있다.
- runOnUiThread 사용하기
안드로이드에서는 UI 자원에 여러 쓰레드가 동시에 접근해서 동기화 이슈를 발생시키지 않기 위해서 UI 작업은 메인쓰레드(UI쓰레드)에서만 담당하게 된다. 즉, 메인쓰레드는 안드로이드의 앱 화면을 그리는 작업을 하느라 바쁘기 때문에 다른 무거운 작업까지 담당한다면 UI 스레드가 너무 오랫동안 차단되어 ANR(애플리케이션 응답 없음) 오류가 트리거 된다.
따라서, DB 작업이나 네트워크 작업과 같이 무거운 작업은 다른 쓰레드를 통해서 진행해야 한다.
다른 쓰레드를 통해서 작업을 진행한 후, 작업 결과를 UI에 적용하기 위해서는 메인쓰레드에게 작업 결과를 넘겨줘야 한다.
따라서, 스레드 간 메세지를 전달하기 위해 핸들러를 사용한다.
이렇게 메세지나 Runnable 객체를 메인쓰레드에게 넘겨주기 위해 사용하는 방법 중 하나가 runOnUiThread() 메서드이다.
만약, runOnUiThread를 실행하는 쓰레드가 메인쓰레드가 아니라면 메인쓰레드의 event Queue에 해당 작업이 들어가게 되어 수행된다.
계산기 앱에서는 계산 히스토리가 발생할 때마다 history 데이터베이스에 추가해주거나 히스토리 기능을 사용할 때 history 데이터베이스에서 데이터들을 가져오는 DB 작업이 필요하고 이러한 작업을 Thread에서 실행시킨다.
히스토리 버튼을 클릭해서 모든 계산식과 결과값을 DB에서 읽어온 후, 해당 결과들을 TextView에 담아 화면에 띄워야하는 UI 작업이 필요하다. 따라서, DB 작업을 수행한 Thread 내에서 메인쓰레드에게 UI 작업을 할 수 있도록 작업물을 넘겨줘야 한다.
'Android > Side Projects' 카테고리의 다른 글
Pomodoro Timer 앱 개발하기 (0) | 2022.01.29 |
---|---|
전자 액자 앱 개발하기 (0) | 2022.01.28 |
전자 액자 앱 개발하기 (3) | 2022.01.27 |
계산기 앱 개발하기 (0) | 2022.01.25 |
계산기 앱 개발하기 (0) | 2022.01.24 |