16_위시리스트2_Room 사용법1(DB 접근 준비)

소정·2024년 9월 5일
0

Android_with_compose

목록 보기
16/17

android 기기 로컬 데이터 저장 소개

android에 로컬 방식으로 데이터를 저장하면 파일이나 데이터베이스 등 기기에 데이터가 저장된다.
이렇게 하면 성능을 향상 시키고 외부 데이터 소스에 대한 의존성을 줄이는 데 도움이 될 수 있다.
또 민감한 데이터가 인터넷에 노출되는 것을 방지하여 애플리케이션을 더욱 안전하게 만드는 데 도움이 된다

로컬 저장 방식
1) SharedPreferences
간단한 키-값 저장 시스템, 하나의 key에 하나의 값만 저장 가능 (덮어쓴다)
주로 사용자의 기본 설정, 애플리케이션 설정 또는 그대로 유지해야하는 기타 데이터 저장
2) Internal Storage
내부저장소 | 앱 전용 파일 시스템 앱에서만 접근 가능, 다른 앱이나 다른 사용자가 액세스 할 수 없는 비공개 파일을 앱에 저장할 수 있음
3) External Storage
외부 저장소 | SD카드, USB저장소와 같은 장치 내부가 아닌 곳에 있는 저장소 사용
다른 앱이나 사용자와 공유할 파일 저장
4) SQLite & Room
수정 가능한 구조화된 데이터 저장, CRUD
SQLite는 경량 SQL임 쿼리가 복잡해지면 사용하기 불편해짐 그래서 나온 것이 Room


1. Room에 영구적으로 저장하고 싶은 객체 유형 Entity 작성

기존 Wish data class에 @Entity 어노테이션 달아주기

  1. Entity 어노테이션 달기 + 테이블 네임 설정
    @Entity(tableName) : 테이블 네임 설정 필수
  2. primary key 설정
    @PrimaryKey(autoGenerate = true) 자동 증가
  3. 나머지 애들은 컬럼 이름 등록해주기
    @ColumnInfo(name = "wish-title")
    (name = )은 생략 가능
package com.lululalal.wishlist.data

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "wish-table")
data class Wish(
    @PrimaryKey(autoGenerate = true)
    val id : Long =0L,
    @ColumnInfo(name = "wish-title")
    val title:String,
    @ColumnInfo("wish-desc")
    val description:String
)

2. DAO 작성

추상클래스로 작성한다.

DAO 어노테이션
1. @Insert : 저장
2. @Query : all data 또는 select data
3. @Update : 업데이트
4. @Delete : 삭제

Flow 클래스

  • Query 연산을 위한 것, 라이브 데이터와 같은 Flow reactive(리액티브 스트림)을 사용하며 코틀린의 코루틴 라이브러리에 포함되어 있음
  • 비동기 데이터 스트림을 반응형 방식으로 제어함
  • Room과 같이 사용하여 데이터 조작 시 업데이트를 fetch로 가져오고 emit으로 방출하게 해 줌
    kotlinx.coroutines.flow.Flow
package com.lululalal.wishlist.data

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow

//추상 클래스로 작성
//추상 클래스=>구현부가 없는 함수
@Dao
abstract class WishDao {

    //suspend 키워드를 사용해서 백그라운드에서 실행되도록 함
    //getAllWishes와 getByWishId엔 안붙여줘도 되는데 이미 코루틴 방식과 비슷한 Flow를 사용하기 떄문

    // onConflict = OnConflictStrategy.IGNORE = 존재하는 항목이 있으면 무시하고 진행해라
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    abstract suspend fun addWish(wishEntity : Wish)

    @Query("SELECT * FROM `wish-table`")
    abstract fun getAllWishes(): Flow<List<Wish>> //kotlinx.coroutines.flow.Flow 임포트

    @Update
    abstract suspend fun updateWish(wishEntity : Wish)

    @Delete
    abstract suspend fun deleteWish(wishEntity : Wish)

    @Query("SELECT * FROM `wish-table` WHERE id=:id")
    abstract fun getByWishId(id:Long): Flow<Wish>

}

3. 데이터 베이스 Room 생성

  • 추상 클래스로 작성
  • database 키워드 상속 받기 : class WishDatabase : RoomDatabase
  • 데이터베이스 어노테이션 달기 : @Database
  • dao와 데이터베이스 버전을 정의

WishDatabase.kt

@Database(
	entities = [Wish::class], //사용할 엔티티 명시, 배열로 작성
    version = 1, //버전 명시
    exportSchema = false //
)
abstract class WishDatabase : RoomDatabase() {
	abstract fun WishDao() : WishDao //WishDao에 작성한 것에 접근 가능하도록 함
}

5. Repository 작성

  • Repository 설정을 통해 DAO클래스에 작성한 메소드에 접근할 수 있도록함
  • viewModel을 써서 이 저장소가 suspend라는 function을 담당하도록 한다.
  • Room데이터 베이스에 viewModel을 대신하여 소통하는 역할
  • DAO, data access object를 사용해 데이터베이스에 query를 전달
  • Call을 사용해 매소드 호출

WishRepository.kt

package com.lululalal.wishlist.data

import kotlinx.coroutines.flow.Flow

class WishRepository(private val wishDao: WishDao) {

    suspend fun addWish(wish:Wish) {
        wishDao.addWish(wish)
    }

    fun getAllWishes():Flow<List<Wish>> = wishDao.getAllWishes()

    fun getByWishId(id:Long): Flow<Wish> = wishDao.getByWishId(id)

    suspend fun updateWish(wish:Wish) = wishDao.updateWish(wish)

    suspend fun deleteWish(wish:Wish) = wishDao.updateWish(wish)

}

6. ViewModel 만들기

viewModel을 사용해서 데이터의 로딩과 저장 들의 작업을 책임지도록한다

lateinit var 이란?
var 변수에 대한 작업을 call로 호출하기 전에 변수가 초기화될 것이라고 Kotlin compiler(컴파일러)에 약속하는 것

  • 객체 생성 중에 초기화 할 수 없음
  • 초기화 후 non-null 상태여야 하는 경우 사용한다
  • 초기화가 확실하지 않거난 해당 속성에 접근할 때마다 null check를 하기 싫을 떄 사용
  • 주로 Dagger와 같은 dependency injection(종속 항목 삽입)플레임워크를 사용할 때나 메소드>로 property에 assign으로 지정할 때에 사용
  • 레이아웃에 inflate를 쓴 뒤 property에 값을 assign으로 지정할 때 사용
  • primitive type(기본타입)에는 사용 불가

Dispatchers?
코루틴을 어떤 스레드에서 실행시킬 지 결정하는 객체
1. Dispatchers.IO
-io는 input과 output을 의미, 입출력 관련 작업 전문가
-파일의 읽기 및 쓰기, 데이터베이스 작업, 네트워크 호출
-io작업을 한 때 그 스레드가 일을 마칠 때까지 기다려야함 Dispatchers.IO는 공유 스레드 풀을 유지 관리함
-시스템 리소스가 효율적으로 사용되고 메인 스레드를 차단하지 않도록 보장함
2. Dispatchers.Default
3. Dispatchers.main
4. Dispatchers.Unconfined


viewModel.kt

package com.lululalal.wishlist

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lululalal.wishlist.data.Wish
import com.lululalal.wishlist.data.WishRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch

class WishViewModel(
    //pass로 전달하는 WishRepository를 따로 설정할 필요가 없도록 초기 상태를 설정
    private val wishRepository: WishRepository =
):ViewModel() {
    //데이터와 UI간의 소통 책임 - 데이터 저장, 로드, 수집, 수정 둥둥

    var wishTitleState by mutableStateOf("")
    // mutableStateOf사용 ->getValue와 setValue import
    var wishDescriptionState by mutableStateOf("")

    fun onWishTitleChanged(newString: String) { //외부에서 받아온 string
        wishTitleState = newString // mutableStateOf 타입인 변수에 덮어쓰기
    }
    fun onWishDescriptionChanged(newString: String) {
        wishDescriptionState = newString
    }

    lateinit var getAllWishes:Flow<List<Wish>> //Flow를 사용하면 비동기 상태이기 때문에 lateinit 같이 써야한다
    //lateinit var ?
    //var 변수에 대한 작업을 call로 호출하기 전에 변수가 초기화될 것이라고 Kotlin compiler(컴파일러)에 약속하는 것
    // 객체 생성 중에 초기화 할 수 없음
    // 초기화 후 non-null 상태여야 하는 경우 사용한다
    // 초기화가 확실하지 않거난 해당 속성에 접근할 때마다 null check를 하기 싫을 떄 사용
    // 주로 Dagger와 같은 dependency injection(종속 항목 삽입)플레임워크를 사용할 때나 메소드로 property에 assign으로 지정할 떄에 사용
    // 레이아웃에 inflate를 쓴 뒤 property에 값을 assign으로 지정할 때 사용
    // primitive type(기본타입)에는 사용 불가

    init {
        viewModelScope.launch { // getAllWishes를 코루틴스코프의 뷰모델 스코프 내부에서 초기화 -> init 이후 getAllWishes를 empty 상태가 아니게 됨
            getAllWishes = wishRepository.getAllWishes()
        }
    }

    fun addWish(wish: Wish) {
        //코루틴 사용 => 최적화 시켜야함
        viewModelScope.launch(Dispatchers.IO) {
            wishRepository.addWish(wish)
        }
    }
    // Dispatchers?
    // 코루틴을 어떤 스레드에서 실행시킬 지 결정하는 객체
    //1. Dispatchers.IO
    // - io는 input과 output을 의미, 입출력 관련 작업 전문가
    // - 파일의 읽기 및 쓰기, 데이터베이스 작업, 네트워크 호출
    // - io작업을 한 때 그 스레드가 일을 마칠 때까지 기다려야함 Dispatchers.IO는 공유 스레드 풀을 유지 관리함
    // - 시스템 리소스가 효율적으로 사용되고 메인 스레드를 차단하지 않도록 보장함
    //2. Dispatchers.Default
    //3. Dispatchers.main
    //4. Dispatchers.Unconfined

    fun updateWish(wish: Wish) {
        //코루틴 사용 => 최적화 시켜야함
        viewModelScope.launch(Dispatchers.IO) {
            wishRepository.updateWish(wish)
        }
    }

    fun deleteWish(wish: Wish) {
        //코루틴 사용 => 최적화 시켜야함
        viewModelScope.launch(Dispatchers.IO) {
            wishRepository.deleteWish(wish)
        }
    }

    fun getByWishId(id:Long):Flow<Wish> = wishRepository.getByWishId(id)

}



Room(DB)에 데이터 저장 작업 순서

1.data class
- 저장하려는 정보가 될 data class 필요
2.DAO(Data Access Object)
- 데이터 교환을 위한 객체
- DB의 데이터가 Presentation Logic Tier로 넘어오게 될 때는 DTO의 모습으로 바껴서 오고가는 것
- CRUD작업 작성 및 어느 data class에서 어떻게 작동하는지 작성
3.Repository
- DB에 접근하는 객체
- 데이터베이스 테이블에 접근하는 메서드들을 사용하기 위한 인터페이스
- Persistence Layer(DB에 data를 CRUD하는 계층)
4.viewModel
- view와 model 중간 매개체
- 레포지포리에 작성된 함수를 호출텍스트

profile
보조기억장치

0개의 댓글