액티비티 구현 시 가지는 문제들

  • findViewById() 를 여러 번 호출하는 문제
    이는 느릴 뿐만 아니라, 컴파일할 때 확인하지 않아 안전하지 않으며, ID가 잘못된 경우 런타임에 앱이 종료된다.

  • 레이아웃에서 Button 에 있는 android:onClick 속성을 사용하지만 안전하지 않다.
    메서드가 구현되지 않거나 이름이 변경되면 런타임에 앱이 충돌한다(crash).

  • 많은 코드를 작성해야 한다.
    Activity 및 Fragment 는 매우 빠르게 성장하므로 가능한 코드를 줄이는 게 좋다.
    또한 Activity 및 Fragment 의 코드는 테스트 및 유지 관리가 어렵다.

데이터 바인딩 라이브러리를 사용하면 logic 을 Activity 에서 재사용 가능하고 테스트하기 쉬운 곳으로 이동하여 이러한 문제를 해결할 수 있다.

 

 

레이아웃 표현식( "@{}" )을 사용하여 레이아웃 파일의 구성요소에 바인딩하면 다음과 같은 작업을 수행할 수 있다.

  • 앱 성능 향상
  • 메모리 누수(leak) 및 NPE(Null Pointer Exception) 방지
  • UI 프레임워크 호출을 제거하여 코드 간소화

 

DataBindingUtil

  • 레이아웃에서 ViewBinding 을 만드는 유틸리티 클래스

 

DataBindingUtil.setContentView(Activity activity, int layoutId)

  • Activity 의 ContentView 를 지정된 레이아웃으로 설정하고 연관된 Binding 을 반환한다.
  • Parameter
    • activity : ContentView 를 변경해야 하는 Activity
    • layoutId : 레이아웃의 리소스 ID

 

데이터바인딩을 사용하면 Observable value 가 변경되면 해당 value 가 바인딩되는 UI 요소가 자동으로 업데이트된다.

 

 

데이터바인딩 사용 시 인플레이션 방식

  • binding 변수의 목적
    <data> 블록의 레이아웃 변수를 설정하는데 필요함.
  • Binding 클래스는 라이브러리에 의해 자동으로 생성됨.
    이름은 레이아웃 파일을 기준으로 만들어진다.

 

 

BindingAdapter

  • 모든 View 에 적용가능하다.
  • 레이아웃 표현식이 반환하는 값을 사용한다.
/* 
레이아웃 식이 반환하는 값을 사용한다.
app:hideIfZero 는 레이아웃에서 사용할 attribute 이다.
*/
@BindingAdapter("app:hideIfZero")
fun hideIfZero(view: View, number: Int) {
    view.visibility = if (number == 0) View.GONE else View.VISIBLE
}

 

/*
  attributes 가 누락된경우 컴파일 시간에 에러가 발생한다. (requireAll=true)
  메소드는 이제 3개의 파라미터를 취한다.
  requireAll : 바인딩 어댑터 사용 시기를 정의한다.
   - true : 모든 elements 가 XML 정의에 있어야한다.
   - false : missing attributes 가 null 이 될 경우 boolean 이면 false, primitive 면 0이 된다.
 */
@BindingAdapter(value = ["app:progressScaled", "android:max"], requireAll = true)
fun setProgress(progressBar: ProgressBar, likes: Int, max: Int) {
    progressBar.progress = (likes * max / 5).coerceAtMost(max)
}

 

@BindingAdapter("app:popularityIcon")
fun popularityIcon(view: ImageView, popularity: Popularity) {
    // 인기도에 따른 색깔 정하기
    // context 가 필요하다면 view 를 이용하면 된다.
    val color = getAssociatedColor(popularity, view.context) 

    // 인기도에 따른 이미지 염색하기
    ImageViewCompat.setImageTintList(view, ColorStateList.valueOf(color))

    // 인기도에 따른 이미지 설정하기
    view.setImageDrawable(getDrawablePopularity(popularity, view.context))
}

 

Reference

 

 

'안드로이드' 카테고리의 다른 글

lifecycle-aware architecture component  (0) 2022.11.16
Room 라이브러리 정리  (0) 2022.07.04
데이터베이스  (0) 2022.07.03
디피를 픽셀로 바꾸기  (0) 2022.06.17
화면 내비게이션  (0) 2022.06.07

lifecycle-aware architecture component 소개

 

ViewModel

액티비티 및 프래그먼트는 사용자가 앱과 상호작용할 때 자주 생성 및 파괴되는 단명 객체이므로 이곳에서 데이터를 관리하는 행동은 좋지 않은 선택이다. 

 

ViewModel 은 데이터 조작 및 지속성뿐만 아니라 네트워크 통신과 관련된 작업을 관리하는데 더 적합하다.

 

ViewModel 클래스

  • Activity 또는 Fragment 의 데이터를 준비하고 관리하는 클래스
  • Activity 또는 Fragment 와 애플리케이션의 나머지 부분과의 통신을 처리한다.
  • 항상 scope(Activity 또는 Fragment)와 함께 생성되며 scope가 활성 상태인 한 유지된다.
    구성 변경(예: 회전)을 위해 owner 가 파괴되더라도 ViewModel 이 파괴되지 않은다는 것을 의미하며 새로운 owner는 기존 ViewModel 다시 연결된다.
  • ViewModel의 목적은 Activity 또는 Fragment 에 필요한 정보를 수집하고 보관하는 것이다.
  • Activity, Fragment 는 ViewModel 의 변경사항을 관찰할 수 있어야 한다.
  • ViewModel 은 일반적으로 LiveData 또는 Android Data Bindng 을 통해 정보를 노출한다.
    또한, 즐겨 찾는 프레임워크의 모든 관찰 가능성 구문을 사용할 수 있다.
  • ViewModel 의 유일한 책임은 UI의 데이터를 관리하는 것이다.
  • View 계층에 접근하거나 Activity 또는 Fragment의 참조를 갖고 있어서는 안 된다.
  • Fragment 간의 communication layer 로 사용될 수도 있다.
    각 Fragment 는 Activity를 통해 동일한 키를 사용하여 ViewModel 을 얻을 수 있다.

 

ViewModelStore

  • ViewModel 을 저장하는 클래스
  • ViewModelStore는 새 ViewModel 또는 이전에 만든 ViewModel 을 제공한다.
  • ViewModelStore 의 owner 가 삭제되어 재생성되지 않을 경우 ViewModelStore 에서 clear() 를 호출하여 ViewModel 이 더 이상 사용되지 않음을 알려야 한다.
  • ViewModelStoreOwner.getViewModelStore() 를 사용하여 ViewModelStore 를 검색할 수 있다.

 

ViewModelProvider

  • ViewModel 을 제공하는 유틸리티 클래스

 

LifecycleOwner

AppCompatActivirty 및 Fragment 에서 구현되는 인터페이스로 구현하는 Owner 객체를 다른 구성 요소가 구독하여 소유자의 수명 주기에 대한 변경사항을 관찰할 수 있다.

 

LifecycleOwner 는 Android Lifecycle 을 가진 모든 클래스에서 사용되는 인터페이스로 ComponentActivity 가 LifecycleOwner 를 구현하고 있으며 AppcompatActivity 는 ComponentActivity 의 하위 클래스이다.

 

ViewModel 과 LiveData 는 Lifecycle 에 바인딩할 수 있다.

 

 

LifecycleObserver

  • LifecycleObserver 인터페이스를 구현하는 라이프사이클 인식 객체는 lifecycle owner 의 상태 변화를 observe(관찰) 할 수 있다. 즉, lifecycle owner 에 반응하는 Component 를 만들 수 있다.
  • 이 인터페이스를 직접 사용하지 말고 DefaultLifecycleObserver 또는 LifecycleEventObserver 를 구현하여 라이프사이클 이벤트에 대해 알린다.

DefaultLifecycleObserver

  • LifecycleOwner 의 상태 변화를 수신하기 위한 콜백 인터페이스
  • 클래스가 이 인터페이스와 LifecycleEventObserver 를 모두 구현하는 경우 DefaultLifecycleObserver.onStateChanged() 를 호출한다.
  • 클래스가 이 인터페이스를 구현하는 동시에 @OnLifecycleEvent 를 사용하는 경우 애노테이션은 무시된다.
  • onResume(), onPause() 등의 수명 주기 메서드에 필요한 작업을 넣는다.

 

LifecycleEventObserver

  • 수명 주기 변경을 수신(receive)하고 수신기(receiver)로 보낼 수 있는(dispatch) 클래스
  • onStateChanged() 메서드를 구현한다.

 

 

LiveData

View 를 수정하는 대신 ViewModel 의 데이터 소스를 관찰하고 데이터가 변경될 때 데이터를 수신하도록 Activity 및 Fragment 를 구성하는데 이러한 방법을 관찰자(Observer) 패턴이라고 한다.

 

LiveData 클래스

  • 데이터를 관찰 가능한 것으로 만들려면 LiveData 클래스로 감싼다(wrap).
  • 라이프사이클을 인식하고 활성 관찰자에게만 알리는 관찰 가능한 특수 클래스이다.

 

SavedStateHandle

시스템이 메모리가 부족하면 캐시 내의 프로세스를 삭제한다. 그러나 프로세스가 삭제되더라도 정보가 손실되지 않도록 앱의 상태 또는 일부를 저장하려는 경우가 있다. lifecycle-viewmodel-savedstate 모듈은 ViewModel 에 저장된 상태에 대한 액세스를 제공한다.

 

ViewModel 에 저장된 SavedStateHandle 에 액세스할 수 있다.

SavedStateHandle 을 사용하는 생성자를 구축하고 state 를 private field 에 저장해야 한다.

SavedStateHandle 은 저장된 상태에서 객체를 쓰고 검색할 수 있는 key/value 맵이다.

 

 

 

 

Reference

'안드로이드' 카테고리의 다른 글

DataBinding  (0) 2022.11.17
Room 라이브러리 정리  (0) 2022.07.04
데이터베이스  (0) 2022.07.03
디피를 픽셀로 바꾸기  (0) 2022.06.17
화면 내비게이션  (0) 2022.06.07

Entity 

앱에 데이터를 저장할 때는 저장하려는 객체를 나타내는 Entity를 정의해야 한다.

각 Entity는 연결된 Room 데이터베이스의 테이블에 상응한다.

Entity의 각 인스턴스는 상응하는 테이블의 데이터 행 하나를 나타낸다.

즉, SQL 코드를 작성하지 않고도 Room Entity를 사용하여 데이터베이스 스키마를 정의할 수 있다.

 

Entity 구조

각 Room Entity는 @Entity 애노테이션이 달린 클래스로 정의한다.

Room Entity에는 기본 키(Primary Key)를 구성하는 하나 이상의 columns 비롯하여 데이터베이스의 상응하는 테이블에 있는 각 columns의 필드가 포함되어 있다.

필드를 

다음은 간단히 User 테이블을 정의하는 예시이다.

@Entity
data class User(
    @PrimaryKey val id: Int,

    val firstName: String?,
    val lastName: String?
)

 

기본적으로 Room은 클래스 이름을 테이블 이름으로 사용한다.

테이블 이름을 다르게 하려면 @Entity 애노테이션의 tableName 속성을 설정하면 된다.

Room은 기본적으로 필드 이름을 column 이름으로 사용한다.

column 이름을 다르게 하려면 @ColumnInfo 애노테이션을 필드에 추가하고 name 속성을 설정하면 된다.

참고로 SQLite의 테이블 및 column 이름은 대소문자를 구분하지 않는다.

@Entity(tableName = "users")
data class User (
    @PrimaryKey val id: Int,
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?
)

기본 키(Primary Key) 정의

각 Room Entity는 상응하는 데이터베이스 테이블의 각 column을 고유하게 식별하는 기본 키(Primary Key)를 정의해야 한다.

가장 간단한 방법은 @PrimaryKey 애노테이션을 다는 것이다.

자동 ID를 할당하게 하려면 @PrimaryKey 의 autoGenerate 속성을 true로 설정하면 된다.

@PrimaryKey val id: Int

 

복합 기본 키 정의

Entity 인스턴스가 여러 열의 조합으로 고유하게 식별되도록 하려면 이러한 열을 @Entity의 primaryKeys 속성에 나열하여 복합 기본 키를 정의하면 된다.

@Entity(primaryKeys = ["firstName", "lastName"])
data class User(
    val firstName: String?,
    val lastName: String?
)

 

필드 무시

기본적으로 Room은 Entity에 정의된 각 필드의 column을 생성한다.

Entity의 필드를 유지하지 않으려면 @Ignore 애노테이션을 사용하여 필드에 주석을 달면 된다.

@Entity
data class User(
    @PrimaryKey val id: Int,
    val firstName: String?,
    val lastName: String?,
    @Ignore val picture: Bitmap?
)

Entity가 상위 Entity에서 필드를 상속하는 경우 일반적으로 @Entity 속성의 ignoredColumns 속성을 사용하는 것이 더 쉽다.

open class User {
    var picture: Bitmap? = null
}

@Entity(ignoredColumns = ["picture"])
data class RemoteUser(
    @PrimaryKey val id: Int,
    val hasVpn: Boolean
) : User()

 

테이블 검색 지원 제공

Room은 데이터베이스 테이블에서 세부정보를 더 쉽게 검색할 수 있게 하는 여러 유형의 애노테이션을 지원한다.

minSdkVersion이 16 이상이라면 전체 텍스트 검색(FTS)을 지원한다.

 

전체 텍스트 검색 지원

앱에서 전체 텍스트 검색을 통해 데이터베이스 정보에 매우 빠르게 액세스해야 한다면 FTS3 또는 FTS4 SQLite 확장 모듈을 사용하는 가상 테이블로 Entity를 지원해야 한다. 이 기능은 Room 2.1.0 이상에서 제공된다.

 

FTS 지원 테이블은 항상 INTEGER 유형의 기본 키와 'rowid'라는 column 이름을 사용한다.

FTS 테이블 지원 Entity에서 기본 키를 정의하는 경우 반드시 이러한 유형(INTEGER) 및 column 이름(rowid)을 사용해야 한다.

// Use `@Fts3` only if your app has strict disk space requirements or if you
// require compatibility with an older SQLite version.
@Fts4
@Entity(tableName = "users")
data class User(
    /* Specifying a primary key for an FTS-table-backed entity is optional, but
       if you include one, it must use this type and column name. */
    @PrimaryKey @ColumnInfo(name = "rowid") val id: Int,
    @ColumnInfo(name = "first_name") val firstName: String?
)

 

특정 column index 생성

앱에서 FTS3 또는 FTS4 테이블 지원 Entity를 사용할 수 없는 SDK 버전을 지원해야하는 경우에도 데이터베이스에 있는 특정 column의 index를 생성하여 쿼리 속도를 높일 수 있다. Entity에 index를 추가하려면 @Entity 애노테이션 내에 indices 속성을 포함하여 index 또는 복합 index에 포함하려는 column의 이름을 나열하면 된다.

@Entity(indices = [Index(value = ["last_name", "address"])])
data class User(
    @PrimaryKey val id: Int,
    val firstName: String?,
    val address: String?,
    @ColumnInfo(name = "last_name") val lastName: String?,
    @Ignore val picture: Bitmap?
)

 

특정 필드 또는 필드 그룹이 고유해야하는 경우도 있다.

@Index 애노테이션의 unique 속성을 true로 설정하여 고유 속성으로 만들 수 있다.

@Entity(indices = [Index(value = ["first_name", "last_name"],
        unique = true)])
data class User(
    @PrimaryKey val id: Int,
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?,
    @Ignore var picture: Bitmap?
)

데이터 액세스 객체 (DAO)

Room 지속성 라이브러리를 사용하여 앱 데이터를 저장할 때 데이터 액세스 객체(DAO)를 정의하여 저장된 데이터와 상호작용한다. 각 DAO에는 앱 데이터베이스에 관한 추상 액세스 권한을 제공하는 메서드가 포함되어 있다.

Room은 컴파일 시간에 정의된 DAO 구현을 자동으로 생성한다.

DAO를 사용하여 앱 데이터베이스에 액세스하면 중요한 아키텍처 원칙인 관심사 분리를 유지할 수 있다.

 

DAO 분석

각 DAO를 인터페이스나 추상 클래스로 정의할 수 있다. 일반적으로 인터페이스를 사용한다.

어느 경우든 DAO에는 항상 @DAO 애노테이션을 달아야 한다.

DAO에는 속성이 없지만 데이터베이스의 데이터와 상호작용하는 메서드를 하나 이상 정의한다.

데이터베이스 상호작용을 정의하는 DAO 메서드에는 두가지 유형이 있다.

편의 메서드

  • SQL 코드 작성 없이 데이터베이스에서 행을 삽입하고 업데이트하고 삭제할 수 있다.(INSERT, UPDATE, DELETE)

쿼리 메서드

  • 자체 SQL 쿼리를 작성하여 데이터베이스와 상호작용할 수 있다.

다음 코드는 Room 데이터베이스에서 User 객체를 삽입, 삭제, 조회하는 메서드를 정의하는 간단한 DAO 예이다.

@Dao
interface UserDao {
    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)

    @Query("SELECT * FROM user")
    fun getAll(): List<User>
}

 

편의 메서드

Room은 개발자가 SQL문을 작성하지 않아도 간단한 삽입, 업데이트, 삭제를 실행하는 메서드를 정의한 편의 애노테이션을 제공한다. 좀 더 복잡한 SQL이 필요하다면 쿼리 메서드를 사용하면 된다.

 

Insert 삽입

@Insert 애노테이션을 사용하면 데이터베이스의 적절한 테이블에 매개변수를 삽입하는 메서드를 정의할 수 있다.

@Insert 메서드의 각 매개변수는 @Entity 애노테이션이 달린  클래스의 인스턴스 이거나 @Entity 애노테이션이 달린 클래스 인스턴스의 컬렉션이어야 한다.

@Insert 메서드가 호출되면 Room은 전달된 각 Entity 인스턴스를 상응하는 테이블에 삽입(Insert)한다.

@Insert 메서드가 단일 매개변수를 수신하면 long 값을 반환할 수 있고 이 값은 삽입된 Entity의 새 rowId 이다.

매개변수가 배열이나 컬렉션이면 메서드는 long값의 배열이나 컬렉션을 반환해야 하며 각 값은 삽입된 Entity 들의 rowId이다.

@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUsers(vararg users: User)

    @Insert
    fun insertBothUsers(user1: User, user2: User)

    @Insert
    fun insertUsersAndFriends(user: User, friends: List<User>)
}

Update 업데이트

@Update 애노테이션을 사용하면 특정 행을 업데이트하는 메서드를 실행할 수 있다.

@Insert 와 마찬가지로 매개변수로 Entity 인스턴스를 허용한다.

Room은 기본 키를 사용하여 전달된 Entity 인스턴스를 데이터베이스의 행과 일치시킨다.

기본 키가 같은 행이 없으면 Room에서는 아무것도 변경하지 않는다.

@Update 메서드는 성공적으로 업데이트 된 행 수를 나타내는 Int 값을 선택적으로 반환한다.

@Dao
interface UserDao {
    @Update
    fun updateUsers(vararg users: User)
}

 

Delete 삭제

@Delete 애노테이션을 사용하면 특정 행을 삭제하는 메서드를 실행할 수 있다.

@Insert 와 마찬가지로 매개변수로 Entity 인스턴스를 허용한다.

Room은 기본키를 사용하여 전달된 Entity 인스턴스를 데이터베이스의 행과 일치시킨다.

기본 키가 같은 행이 없으면 Room에서는 아무것도 변경하지 않는다.

@Delete 메서드는 성공적으로 삭제된 행 수를 나타내는 Int를 값을 선택적으로 반환한다.

@Dao
interface UserDao {
    @Delete
    fun deleteUsers(vararg users: User)
}

 

쿼리 메서드

@Query 애노테이션을 사용하면 SQL 문을 작성하여 DAO 메서드로 노출할 수 있다.

Room은 컴파일 시간에 SQL 쿼리를 검증합니다. 즉, 쿼리에 문제가 있으면 런타임 실패가 아닌 컴파일 오류가 발생합니다.

 

단순한 쿼리

@Query("SELECT * FROM user")
fun loadAllUsers(): Array<User>

 

테이블 열의 하위 집합 반환

대부분의 경우 쿼리하는 테이블에서 열의 하위 집합만 반환하면 된다. 

리소스를 절약하고 쿼리 실행을 간소화하려면 필요한 필드만 쿼리해야 한다.

Room을 사용하면 결과 열 세트를 반환된 객체에 매핑할 수 있는 한 모든 쿼리에서 간단한 객체를 반환할 수 있다.

Room은 쿼리에서 first_name 과 last_name 열의 값을 반환하고 이러한 값을 NameTuple 클래스의 필드에 매핑될 수 있다는 것을 인식한다. 쿼리가 반환된 객체의 필드에 매핑되지 않는 열을 반환하면 Room에서는 경고를 표시한다.

data class NameTuple(
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?
)
@Query("SELECT first_name, last_name FROM user")
fun loadFullName(): List<NameTuple>

 

쿼리에 단순 매개변수 전달

대부분의 경우 DAO 메서드는 필터링 작업을 실행할 수 있도록 매개변수를 허용해야 한다.

Room은 쿼리에서 메서드 매개변수를 결합 매개변수로 사용하는 것을 지원한다.

@Query("SELECT * FROM user WHERE age > :minAge")
fun loadAllUsersOlderThan(minAge: Int): Array<User>

쿼리에서 여러 매개변수를 전달하거나 같은 매개변수를 여러 번 참조할 수도 있다.

@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>

@Query("SELECT * FROM user WHERE first_name LIKE :search " +
       "OR last_name LIKE :search")
fun findUserWithName(search: String): List<User>

 

쿼리에 매개변수 컬렉션 전달

일부 DAO 메서드를 사용하면 런타임까지 알 수 없는 가변적인 수의 매개변수를 전달해야 할 수 있다.

Room은 매개변수가 언제 컬렉션을 나타내는지 인식하고 제공된 매개변수 수에 따라 런타임 시에 자동으로 확장한다.

@Query("SELECT * FROM user WHERE region IN (:regions)")
fun loadUsersFromRegions(regions: List<String>): List<User>

 

여러 테이블 쿼리

일부 쿼리는 결과를 계산하기 위해 여러 테이블에 액세스해야 할 수 있다.

SQL 쿼리에서 JOIN 절을 사용하여 테이블을 두 개 이상 참조할 수 있다.

@Query(
    "SELECT * FROM book " +
    "INNER JOIN loan ON loan.book_id = book.id " +
    "INNER JOIN user ON user.id = loan.user_id " +
    "WHERE user.name LIKE :userName"
)
fun findBooksBorrowedByNameSync(userName: String): List<Book>

데이터베이스

데이터베이스를 보유할 클래스를 정의한다.

이 클래스는 데이터베이스 구성을 정의하고 영구 데이터에 대한 앱의 기본 액세스 포인트 역할을 한다.

데이터베이스 클래스는 다음 조건을 충족해야 한다.

  • 클래스에는 데이터베이스와 연결된 데이터 Entity를 모두 나열하는 entities 배열이 포함된 @Database 애노테이션이 달려야한다.
  • 클래스는 RoomDatabase를 확장하는 추상 클래스여야 한다.
  • 데이터베이스 클래스는 인수가 0개인 데이터베이스와 연결된 각 DAO 클래스의 인스턴스를 반환하는 추상 메서드를 정의해야 한다.
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

앱이 단일 프로레스에서 실행되면 데이터베이스 클래스 객체를 인스턴스화할 때 싱글톤 디자인 패턴을 따라야 한다.

각 RoomDatabase 인스턴스는 리소스를 상당히 많이 소비하며 단일 프로세스내에서 여러 인스턴스에 액세스해야 하는 경우는 거의 없다.


사용 방법

데이터 Entity와 DAO, 데이터베이스 객체를 정의한 후에는 다음 코드를 사용하여 데이터베이스 인턴스를 만들 수 있다.

val db = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java, "database-name" // 데이터베이스 클래스
        ).build()

 

데이터베이스 클래스의 추상 메서드를 사용하여 DAO 인스턴스를 가져올 수 있다.

결과적으로 DAO 인스턴스의 메서드를 사용하여 데이터베이스와 상호작용할 수 있다.

val userDao = db.userDao()
val users: List<User> = userDao.getAll()

 

 

Reference

Room : https://developer.android.com/training/data-storage/room?hl=ko#kts

Room Entity : https://developer.android.com/training/data-storage/room/defining-data#ignore

Room DAO : https://developer.android.com/training/data-storage/room/accessing-data?hl=ko 

 

 

'안드로이드' 카테고리의 다른 글

DataBinding  (0) 2022.11.17
lifecycle-aware architecture component  (0) 2022.11.16
데이터베이스  (0) 2022.07.03
디피를 픽셀로 바꾸기  (0) 2022.06.17
화면 내비게이션  (0) 2022.06.07

SQLite

앱의 화면에서 데이터를  입력하면 서버에 보낼 수도 있고 단말에 저장할 수도 있다.

저장소로 많이 사용되는 것이 데이터베이스이며, 안드로이드에서는 SQLite 라는 관계형 데이터베이스를 제공한다.

SQLite는 오픈 소스로 만들어진 파일 기반의 데이터베이스이다.

용량이 적고 가볍게 동작하면서도 관계형 데이터베이스를 위한 SQL 실행이 가능하다.

대부분의 관계형 데이터베이스는 SQL만으로도 데이터 정의 및 조작이 가능하다.

데이터베이스는 저장소이며 앱에서는 파일로 만들어진다.

데이터베이스를 만들어 두면 다음부터는 만드러진 데이터베이스를 오픈하여 사용한다.

 

 

데이터베이스

데이터베이스는 저장소이며 관계형 데이터베이스는 여러 유형의 데이터베이스 중 하나이다.

관계형 데이터베이스는 저장소 안에 여러 개의 테이블을 만들 수 있으며 테이블은 엑셀의 Sheet 처럼 생각하면 쉽다.

테이블을 정의하면 각 칼럼이 갖는 자료형에 맞게 데이터를 추가할 수 있다.

 

테이블

 데이터베이스는 저장된 데이터를 빠르게 조회하는데 목적이 있기 때문에 들어있는 데이터를 어떻게 찾아낼지에 대한 연구가 오랫동안 진행되어 왔다.

테이블은 릴레이션이라고 부르기도 한다.

한줄로 추가되는 데이터는 레코드(Record), 투플(Tuple) 이라고 부른다.

세로 줄은 칼럼(Column) 또는 애트리뷰트(Attribute)라고 부른다.

각각의 셀을 필드(Field)라고 부르지만 세로 줄을 필드라고 부르는 경우도 있어서 혼동될 수 있어, 칼럼이라고 기억하는게 편하다.

 

SQL 사용

 데이터를 저장한 형태보다 중요한 것이 데이터를 추가하고 조회하는 일이다.

데이터를 조회회할 때 SQL을 사용한다. 

SQL은 데이터를 조작할 때 사용할 수 있는 언어이며 테이블을 만들거나 데이터를 추가하거나 데이터를 조회할 때 사용할 수 있다.

 

테이블 생성하기

CREATE TABLE tablename (attribute1, attribute2, ...) options ;

 

데이터 추가하기

INSERT INTO tablename (attribute1, attribute2, ..) VALUES (data1, data2, ..);

 

데이터 조회하기 

SELECT attribute1, attribute2 FROM tablename WHERE condition ORDER BY attribute1, ... [ASC|DESC];

WHERE 뒤에는 조건이 올 수 있다.

 

헬퍼 클래스 사용하기

 헬퍼 클래스는 데이터베이스의 생성, 오픈, 업그레이드, 다운그레이드를 할 때 유용하다.

헬퍼 클래스는 데이터베이스 버전을 지정하여 데이터베이스를 업그레이드할 때 알 수 있도록 한다.

현재 단말에 설치된 앱의 테이블이 새로 만들어지는지 아니면 업그레이드되어야 하는지 알려준다.

 

onCreat() 메소드는 데이터베이스가 처음 만들어지는 경우에 호출되므로 데이터베이스를 초기화(생성)하는 SQL을 넣어준다.

onUpgrade() 메소드는 테이블이 변경되어야 하는 등 단말에 저장된 데이터베이스의 구조가 바뀌어야하는 경우에 사용할 수 있다. 테이블을 변경하기 위한 ALTER 문 등을 넣을 수 있으며, 필요한 경우 이미 저장되어 있는 데이터를 다른 곳에 복사했다가 새로 테이블을 만들고 그 테이블에 넣어주는 방식으로 처리하기도 한다.

 

 

인터넷 연결 상태

 인터넷 연결 상태는 크게 연결된 상태와 연결되지 않은 상태로 나눌 수 있다.

연결된 상태는 무선 랜으로 연결된 상태와 3G/LTE로 연결된 상태로 나눌 수 있다.

(유선랜 연결이 있을 수 있지만 대부분이 무선랜, 3G/LTE 이다)

 

인터넷 연결 상태 확인하기

 연결상태를 확인할 때는 시스템 서비스 객체 중에 ConnectivityManager 객체를 사용한다.

ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

연결상태를 확인하는 경우가 자주 생긴다면 이 코드를 하나의 클래스로 정의하는 것이 더 편리하다.

ConnectivityManager 객체의 getActiveNetworkInfo 메소드를 호출하면 NetworkInfo 객체가 반환되고 그 안에 상태 정보가 있다.

 

연결 상태를 확인할 때 ACCESS_NETWORK_STATE 권한이 있어야 한다.

 

 

 

안드로이드 기기별로 제곱인치당 픽셀을 다양하게 지원한다.

제곱인치당 160 픽셀을 사용할 수도 있고 같은 공간에 480 픽셀을 사용할 수도 있다.

그래서 가장 피해야할 행동은 픽셀을 이용하여 거리나 크기를 정의하는 것이다.

밀도가 다른 화면에서 UI 크기를 유지하려면 밀도 독립형 픽셀(dp)를 측정 단위로 사용해야 한다.

1dp는 160dpi 기준의 1px과 거의 동일한 가상 픽셀 단위이다.

안드로이드는 이 1dp 값을 밀도마다 적합한 픽셀 수로 변환한다.

 

그럼 어떻게 dp를 픽셀로 변환할까? dp 단위를 픽셀로 변환하는 것은 간단하다.

px = dp * (dpi/160)

어렵지 않은 계산 식이다. 그럼 코드 상에서는 어떻게 dp를 픽셀로 바꿀 수 있을까?

val scale: Float = resources.displayMetrics.density

간단하게 1줄로 해결 할 수 있다.

scale 변수의 값은 기기에서의 1dp를 1px로 변환한 값을 얻을 수 있다.

 

Resources 클래스는 애플리케이션의 리소스에 접근하기 위한 클래스이다.

DisplayMetrics 클래스는 크기, 밀도 및 글꼴 크기 조정과 같은 디스플레이에 대한 일반 정보를 설명하는 구조입니다.

DisplayMetrics 클래스의 density 속성은 디스플레이의 논리적 밀도입니다.

 

 

Reference

  • https://developer.android.com/training/multiscreen/screendensities?hl=ko
  • https://developer.android.com/reference/android/content/res/Resources?hl=ko#getDisplayMetrics()
  • https://developer.android.com/reference/android/util/DisplayMetrics?hl=ko

'안드로이드' 카테고리의 다른 글

lifecycle-aware architecture component  (0) 2022.11.16
Room 라이브러리 정리  (0) 2022.07.04
데이터베이스  (0) 2022.07.03
화면 내비게이션  (0) 2022.06.07
화면 여러 개 만들기 : 애플리케이션 구성 요소  (0) 2022.06.03

프래그먼트 Fragment

  • 액티비티로 만든 화면 안에 부분 화면을 독립적으로 사용할 수 있는 방법이 프래그먼트다.
  • 프래그먼트는 액티비티를 본 떠 만든 것이라 액티비티가 동작하는 방법과 유사하다.
  • 액티비티 위에 올라가도록 만들었기 때문에 액티비티가 시스템 역할을 한다.
  • 액티비티보다 훨씬 가볍게 화면 구성을 할 수 있다는 장점이 있다.
  • 프래그먼트는 액티비티 위에 올라가 있을 때만 프래그먼트로서 동작할 수 있다.
  • 프래그먼트에도 수명주기 메소드가 있으며 액티비티에 없는 onAttach 와 onDetach 메소드가 있다.
    • onAttach : 액티비티 위에 올라갈 때 자동으로 호출해준다.
    • onDetach : 액티비티에서 내려올 때 자동으로 호출해준다.
  • 프래그먼트는 프래그먼트 매니저가 관리하기 때문에 프래그먼트 매니저를 통해 추가하거나 교체한다.
  • 프래그먼트와 액티비티 간에는 메소드 호출을 통해 데이터 전달이나 필요한 기능을 수행하도록 할 수 있다
  • 프래그먼트에서 액티비티의 메소드를 호출할 때는 인터페이스를 사용하는 것이 일반적이다.

 

액션바 ActionBar

  • 화면 위쪽에 타이틀 부분이 액션바이다.
  • 메뉴를 쉽게 넣을 수 있게 옵션메뉴라는 것이 제공되며, 이미지나 뷰도 액션바에 넣을 수 있다.
  • 액션바를 보이지 않도록 만들거나 여러 가지 기능을 넣는 경우도 있다.

 

탭 Tab

  • 하나의 화면에 여러 가지 서브 화면들을 넣어 둘 수 있는 장점이 있다.
  • 액션바 안에 넣어서 만들 수 있다.

 

뷰 페이저 ViewPager

  • 좌우 스크롤하여 넘겨 볼수 있는 기능을 제공한다.
  • 여러 개의 아이템 중에 하나를 선택하여 보기 위해 화면을 스크롤하는 것이므로 선택 위젯으로 만든다.

 

타이틀스트립 TitleStrip

  • 뷰페이저 안에 들어있는 전체 아이템의 개수와 현재 보고 있는 아이템이 어떤 것인지 표시해준다.
  •  

 

바로가기 메뉴

  • 안드로이드에서 NavigationDrawer 라는 이름으로 만들어서 제공하며 프래그먼트로 구성된다.
  • 바로가기 메뉴는 몇 개의 화면에서 공통으로 보이도록 할 수 있기 때문에 빠르게 메뉴 기능에 접근하고자 할 때 사용한다.

'안드로이드' 카테고리의 다른 글

lifecycle-aware architecture component  (0) 2022.11.16
Room 라이브러리 정리  (0) 2022.07.04
데이터베이스  (0) 2022.07.03
디피를 픽셀로 바꾸기  (0) 2022.06.17
화면 여러 개 만들기 : 애플리케이션 구성 요소  (0) 2022.06.03

애플리케이션 구성 요소

  • 시스템은 애플리케이션 구성 요소를 관리하며 어떤 구성 요소가 추가 되었는지 반드시 알고 있어야 한다.
  • 애플리케이션 구성 요소를 만들면 AndroidManifest.xml 파일에 추가해야 한다.
  • 앱이 설치되면 시스템이 AndroidManifest.xml 파일을 보고 구성요소를 확인할 수 있다.
  • 애플리케이션 구성요소 4가지
    • 액티비티 Activity
    • 서비스 Service
    • 브로드캐스트 리시버 Broadcast Receiver
    • 컨텐트 프로바이더 Content Provider

 

액티비티 Activity

  • 앱을 실행했을 때 보이는 화면
  • 액티비티는 안드로이드 태스크(Task)에서 스택(Stack)형태로 관리된다.
  • 새로운 액티비티를 화면에 보여주고 싶으면 인텐트 객체를 이용해 시스템에 요청한다.
  • 액티비티는 액티비티 스택(Activity Stack)에서 액티비티가 관리된다.

 

인텐트 Intent

  • 화면을 실행하는 일은 안드로이드 시스템에서 담당한다.
  • 인텐트는 시스템으로 전달되는 일종의 명령을 담고 있는 객체다.
  • 애플리케이션 구성요소들은 시스템에서 관리하므로 인텐트를 이용해 정보를 주고 받는다.
  • 플래그 Flag
    • 시스템이 인텐트를 해석한 후 무언가를 동작시킬 때 옵션에 따른 동작을 하도록 만들 수 있다.
    • FLAG_ACTIVITY_SINGLE_TOP : 같은 액티비티가 층층이 쌓이는 문제를 해결할 수 있다.
    • FLAG_ACTIVITY_NEW_TASK : 액티비티들을 관리하는 태스크(TASK) 객체를 새로 만들도록 한다.
      • 화면이 없는 곳에서 새로운 화면을 띄워주려면 먼저 태스크 객체가 만들어져야하므로 이 플래그를 추가한다.
    • FLAG_CLEAR_TOP : A액티비티가 이미 만들어져 있고 그 이후에 B,C,D 액티비티가 만들어진 경우 A액티비티를 찾아 다시 띄울 때 이후에 만들어진 B,C,D 액티비티들은 없애주는 역할을 한다.
  • 부가데이터 ExtraData
    • 인텐트는 시스템을 통해 다른 애플리케이션 구성요소로 전달될 수 있다.
    • 인텐트 안에 번들(Bundle) 객체가 들어있어 다른 구성요소들 간에 데이터를 전달하는데 인텐트가 사용된다.
    • 기본 자료형의 데이터 외에 객체의 정보를 전달하고 싶을 때는 Serializable 보다 좀 더 메모리 용량을 적게 차지하는 Parcelable 객체를 만들어 전달할 수 있다.
    •  

 

액티비티 수명주기

  • 액티비티의 상태가 변할 때마다 안드로이드 시스템에서 각 상황에 맞는 메소드를 자동으로 호출해줌으로써 각각 상태에 맞는 코드를 넣어 액티비티의 상태에 따라 제어할 수 있게 해준다.
  • 수명주기 메소드가 자동 호출되도록 만든 이유는 사용자가 입력했던 데이터를 복구하거나 상태 정보를 복구할 수 있도록 만들기 위해서입니다.
  • onResume
    • 화면이 다시 보일 때 항상 호출되므로 이때 저장해 둔 데이터를 가져와 화면에 설정할 수 있다.
  • onPause
    • 화면이 눈에서 보이지 않게 될 때 항상 호출되므로 이때 데이터를 저장해둘 수 있다.
  • onSaveInstanceState
    • 액티비티가 중지되기 전에 onSaveInstanceState 메소드가 호출되는데 이 안에서 임시로 데이터를 저장할 수 있다.
    • 저장할 때는 번들 객체를 만들어 저장한다.
  • onRestoreInstanceState
    • 만든 번들 객체는 액티비티가 다시 만들어질 때 호출되는 onCreate 메소드나 화면에 다시 보일 때 호출되는 onRestoreInstanceState 메소드의 파라미터로 전달된다.

 

서비스 Service

  • 화면 없이 백그라운드에서 실행되는 하나의 단위를 서비스라고 부른다.
  • 화면이 없는 상태에서 실행되는 애플리케이션 구성요소다.
  • 화면이 없어도 데이터를 주고받는 기능을 실행하고 때로는 메시지를 받아서 처리하는데 사용된다.
  • 서비스는 항상 실행되어 있을 수 있도록 비정상 종료되는 상황이 벌어지더라도 시스템에 의해 자동으로 재시작된다.
  •  

 

브로드캐스트 수신자 Broadcast Receiver

  • 브로드캐스팅이란 방송의 의미로 여러 대상에게 동시에 데이터를 전달한다는 뜻이다.
  • 안드로이드의 여러 앱에 어떤 메세지를 전달하고 싶은데 어떤 앱이 이 메세지를 받기를 원하는지 모른다면 브로드캐스팅 방법으로 전달할 수 있다.
  • 앱에 브로드캐스트 수신자라는 것을 만들어 원하는 브로드캐스팅 메세지만 받을 수 있다.
  • 인텐트 필터로 받고 싶은 메세지를 정할 수 있다.

 

위험권한 부여하기

  • 위험 권한이라는 것은 마시말로(API LEVEL 23) 부터 적용된 것으로, 사용자가 좀 더 확실하고 엄격한 방법으로 앱에 권한을 부여하도록 만들어진 것이다.
  • 일반 권한과 위험 권한으로 나눠진다.
  • 일반 권한은 앱을 설치할 때만 승인하면 된다.
  • 위험 권한은 설치 시에 부여한 권한 외에도 앱이 실행된 후에 사용자에게 권한 허용을 요청해야 한다.
  • 사용자가 권한을 부여하지 않으면 해당 기능은 동작하지 않는다.

'안드로이드' 카테고리의 다른 글

lifecycle-aware architecture component  (0) 2022.11.16
Room 라이브러리 정리  (0) 2022.07.04
데이터베이스  (0) 2022.07.03
디피를 픽셀로 바꾸기  (0) 2022.06.17
화면 내비게이션  (0) 2022.06.07

+ Recent posts