5주차는 코어멤버분께서 만들어주신 강의자료들을 정독하면서 공부하였습니다☺️
Preferences Datastore, Room을 활용한 로컬 데이터 저장
대부분의 정보는 서버에 저장하더라도 클라이언트에서 정보를 저장하는 경우가 존재하기 때문에, android에서도 Database 학습 필요!
대표 적인 로컬 저장 방법에는 Preferences Datastore, Room가 존재
1.Preferences DataStore
- Jetpack 라이브러리로 키-값으로 저장하는 저장소
- 코틀린의 코루틴과 Flow를 사용하여 비동기적으로 데이터 저장
2.Room
- Jetpack 라이브러리 중 하나로 원하는 데이터를 로컬 데이터 베이스에 저장, 유지 가능
- SQLite 기반의 관계형 데이터 베이스 구조 → 많은 양의 데이터를 지속적으로 관리해야할 때 사용
- SQL문 사용
- Room의 구성 요소
- Database: 데이터 연결을 위한 기본 엑세스 포인트 역할
- Entity: 데이터베이스 내의 테이블
- DAO (Data Access Object): 데이터 베이스에 접근하는 함수 제공
DataStore를 활용한 로그인 실습
- Dependency 추가
- DataStore Instance 생성 → 매개변수는 필수적으로 입력해야하며 DataStore의 이름을 입력하면 됨
- DataStore Key 정의 : 저장하고자하는 데이터의 형태에 따라 Key 이름 입력 ( 문자열 저장시 stringPreferencesKey, 정수를 저장하고 싶다면 intPreferencesKey를 사용 ), 객체 저장도 가능
- 값 저장하기 : DataStore.edit() 함수를 통해 저장 가능
- 값 불러오기 : Preferences DataStore은 Flow<T> 형식의 값을 반환
package com.gdg.android.presentation.main
import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import androidx.lifecycle.lifecycleScope
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.gdg.android.presentation.login.LoginScreen
import com.gdg.android.presentation.user.UserScreen
import com.gdg.android.ui.theme.GDGAndroidTheme
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "auto_login")
class MainActivity : ComponentActivity() {
private val AUTO_LOGIN_KEY = booleanPreferencesKey("auto_login")
suspend fun saveAutoLoginState(context: Context, isLoggedIn: Boolean) {
context.dataStore.edit { preferences ->
preferences[AUTO_LOGIN_KEY] = isLoggedIn
}
}
fun getAutoLoginState(context: Context): Flow<Boolean> {
return context.dataStore.data
.map { preferences ->
preferences[AUTO_LOGIN_KEY] ?: false // 기본값은 false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
lifecycleScope.launch {
val isLoggedIn = getAutoLoginState(applicationContext).first() // 자동 로그인 상태 확인
setContent {
val navController = rememberNavController()
GDGAndroidTheme {
NavHost(
navController = navController,
startDestination = if (isLoggedIn) "main" else "login" // 자동 로그인 여부에 따라 시작 화면 설정
) {
composable("login") {
LoginScreen(navController)
}
composable("main") {
MainScreen(navController)
}
composable("user") {
UserScreen(navController)
}
}
}
}
}
}
}
Room을 활용한 CRUD 실습
- Dependency 추가
- Database 생성
- @Database를 통해 Entity와 version 정보 상단에 작성 → 테이블 구조가 변경될 때마다 version 갱신
- RoomDatabase를 확장하는 추상 클래스와 DAO 클래스의 인스턴스를 반환하는 추상 메소드 정의
- 데이터 베이스의 여러 인스턴스가 동시에 접근하는 것을 막기 위해 @Volatile 어노테이션으로 싱글톤 패턴 적용
@Database(entities = [UserEntity::class], version = 1)
abstract class UserDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: UserDatabase? = null
fun getDatabase(context: Context): UserDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
UserDatabase::class.java,
"user_database"
).build()
INSTANCE = instance
instance
}
}
}
}
3. Entity 생성
- @Entity를 통해 테이블명 설정
- 고유 식별 키는 @PrimaryKey로 선언하여 값이 자동으로 할당되도록 autoGenerate 값 true로 설정
- @ColumnInfo를 통해 필드 이름 설정
@Entity(tableName = "user") // 테이블명을 user로 설정
data class UserEntity(
@ColumnInfo(name = "name") val name: String,
@ColumnInfo(name = "email") val email: String,
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") val id: Int = 0
)
4.DAO 생성
- @INSERT, @QUERY, @UPDATE, @DELETE 어노테이션을 활용하여 쿼리 작성
- DAO에 구현된 메소드를 통해 데이터 조회, 조작, 관리
@Dao
interface UserDao {
@Insert
fun insert(user: UserEntity)
@Delete
fun delete(user: UserEntity)
@Query("SELECT * FROM user")
fun selectAll(): List<UserEntity>
}
코루틴으로 비동기 데이터 불러오기
코루틴이란?
- 비동기 프로그래밍을 쉽게 할 수 있도록 도와주는 Kotlin 기능
- 코루틴을 사용하면 함수가 작업을 시작한 후 결과를 기다리는 동안 다른 작업 계속 진행 가능
코루틴의 주요 구성 요소
- CoroutineScope: 코루틴이 실행되는 범위로, GlobalScope, CoroutineScope, lifecycleScope 등 존재, 각 스코프는 코루틴의 생명 주기 관리
- CoroutineContext (Dispatcher): 코루틴이 어떤 스레드에서 실행될지를 지정
- Dispatchers.Main: 메인 스레드에 실행, 주로 UI 작업에 사용됨
- Dispatchers.IO: I/O 작업에서 주로 사용됨
- Dispatchers.Default: CPU 집약적인 작업에서 사용
3.suspend 함수: suspend 키워드를 붙여서 만든 함수로 코루틴 내부에서만 호출할 수 있으며, 일반적인 함수처럼 작업이 끝날 때까지 차단하지 않고 비동기적으로 실행
코루틴 사용 예시 로직
- 사용자가 정보를 입력하고 “등록하기” 버튼 클릭시 onClick 함수 실행
- 코루틴을 사용하여 비동기적으로 데이터 삽입 작업 시작 → launch 코드 활용
- withContext(Dispatchers.IO)를 통해 Room 데이터베이스 작업이 백그라운드에서 수행: UserEntity 객체를 만들어 insert 호출하여 데이터 저장
→ 이 과저을 통해 메인 스레드에서 데이터 베이스 삽입의 작업이 실행되지 않도록하여, 앱이 멈추거나 느려지지 않게 처리 함
비동기 데이터 불러오기
//UserScreen.kt
val context = LocalContext.current
val roomDB = UserDatabase.getDatabase(context)
val coroutineScope = rememberCoroutineScope()
val userList = remember { mutableStateListOf<UserEntity>() }
// 데이터를 비동기로 불러오기
LaunchedEffect(Unit) {
coroutineScope.launch {
val users = withContext(Dispatchers.IO) {
roomDB.userDao().selectAll() // 모든 유저 데이터 가져오기 (백그라운드)
}
userList.clear()
userList.addAll(users)
}
}
- LaunchedEffect(Unit)이 사용되어 유저 데이터를 백그라운드 스레드에서 불러옴
- Room 데이터 베이스에서 유저 정보를 가져오는 작업이 Dispatchers.IO 스레드에서 비동기로 실행
@Composable
fun UserCreateItem(
user: UserEntity,
onDeleteClick: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 10.dp, horizontal = 18.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column {
Text(text = user.name, color = Color.Black)
Spacer(modifier = Modifier.height(4.dp))
Text(text = user.email, color = Color.Gray)
}
Icon(
modifier = Modifier.clickable { onDeleteClick() }, // 삭제 클릭 이벤트
imageVector = Icons.Filled.Delete,
contentDescription = null,
)
}
HorizontalDivider(
modifier = Modifier.fillMaxWidth(),
thickness = 1.dp,
color = Color.LightGray
)
}
- 삭제 아이콘 클릭시 onDeleteClick 콜백이 호출됨
→ 코루틴을 통하여 작업 실행
백스택 관리
- popUpTo와 inclusive는 네비게이션에서 백스택을 정리할 때 사용하는 주요 파라미터
- popUpTo("login")처럼 특정 목적지를 지정하여 그 전의 모든 백스택을 비울 수 있고, inclusive는 지정한 목적지까지 포함해 제거할지 결정
- popUpTo(navController.graph.startDestinationId)는 시작 지점까지 돌아가면서 백스택을 비울 때 유용
'Group Study (2024-2025) > Android' 카테고리의 다른 글
[Android] 카카오뱅크 클론 코딩 (0) | 2024.11.20 |
---|---|
[Android] 스타일 가이드와 디자인 패턴 (3) | 2024.11.13 |
[Android] Retrofit2를 활용한 서버통신 (2) | 2024.10.29 |
[Android] Android UI 구현 심화 (3) | 2024.10.16 |
[Android] Android UI 구현 기초 (0) | 2024.10.08 |