일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 |
- 안드로이드 hilt
- 안드로이드 JUnit
- 안드로이드 의존성주입
- Android MVVM
- RxJava
- android memory leak
- 안드로이드 mvvm예제
- 리싸이클러뷰 최적화
- 코루틴
- sharedFlow
- 안드로이드 앱 아키텍처 가이드라인 사용법
- coroutine
- 안드로이드 아키텍처 컴포넌트
- 안드로이드 mvvm
- Hilt
- 안드로이드 최적화
- 안드로이드 Mockito
- android clean architecture
- 안드로이드 테스트코드
- 안드로이드 리싸이클러뷰
- 안드로이드 앱 아키텍처 가이드라인 예시
- Android App Architecture Guideline
- android DI
- 안드로이드 Espresso
- 안드로이드 앱 아키텍처 가이드라인 설명
- 안드로이드 앱 아키텍처 가이드라인
- Koin
- MVVM
- 스타트업 코딩테스트
- 안드로이드 클린 아키텍처
- Today
- Total
안드로이드 연구소
[MVVM만들기] Room 본문
지난 포스팅에서 ViewModel, LiveData, DataBinding로 MVVM을 토대로 만들고
Lifecycle, Paging3에 대해서 알아보았습니다.
오늘로 준비한 안드로이드 아키텍처 컴포넌트 끝이납니다.
마지막 안드로이드 아키텍처 컴포넌트는 Room라이브러리입니다.
바로 시작해보겠습니다.
Q1. Room라이브러리가 등장하게된 배경에 대해 설명해주세요.
Room이 도입되기 전에 개발자는 안드로이드에서
SQLite 데이터베이스 작업을 처리하기 위해 보일러플레이트 코드를 수동으로 작성해야 했습니다.
이 보일러플레이트 코드를 작성하는 과정에서 오류가 발생하기 쉽고 다량의 코드를 작성해야했습니다.
Room은 2017년에 안드로이드 아키텍처 컴포넌트에 등장하여
SQLite에 대해 더 높은 수준의 추상화 계층을 제공하여 이러한 문제점을 해결하도록 설계되었습니다.
이를 통해 어플리케이션 데이터베이스를 간소화되고 편리하게 작업할 수 있도록 하였습니다.
또 LiveData 및 ViewModel과 같은 다른 안드로이드 아키텍처 구성 요소와
손쉽게 통합하여 사용할 수 있어 안드로이드 개발자 사이에서 널리 채택되었습니다.
Room라이브러리가 나오기 이전에는
안드로이드 어플리케이션의 디바이스 데이터베이스를 사용하려면
SQLite라는 것을 사용했어야했네요.
근데 SQLite은 많은 코드들을 수동으로 만들어서 사용하고 있었다고 합니다.
아래는 SQLite 예제인데 얼마나 길고 복잡했는지 확인해보시죠.
(코드들은 이해할 필요없고 참고용으로만 올렸습니다.)
1. Contract class 생성
public final class MyContract {
private MyContract() {}
public static class MyTable {
public static final String TABLE_NAME = "my_table";
public static final String COLUMN_ID = "id";
public static final String COLUMN_NAME = "name";
}
}
2. DataBase Helper class 생성
public class MyDatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "my_database.db";
private static final int DATABASE_VERSION = 1;
public MyDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String createTableQuery = "CREATE TABLE " + MyContract.MyTable.TABLE_NAME + " (" +
MyContract.MyTable.COLUMN_ID + " INTEGER PRIMARY KEY," +
MyContract.MyTable.COLUMN_NAME + " TEXT)";
db.execSQL(createTableQuery);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Handle database schema upgrades if needed
}
}
3. Database Operations 실행
public class EntryActivity extends AppCompatActivity {
private EditText nameEditText;
private Button saveButton;
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_entry);
nameEditText = findViewById(R.id.edit_text_name);
saveButton = findViewById(R.id.button_save);
dbHelper = new MyDatabaseHelper(this);
saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = nameEditText.getText().toString().trim();
if (!name.isEmpty()) {
saveData(name);
} else {
Toast.makeText(EntryActivity.this, "Please enter a name", Toast.LENGTH_SHORT).show();
}
}
});
}
private void saveData(String name) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(MyContract.MyTable.COLUMN_NAME, name);
long newRowId = db.insert(MyContract.MyTable.TABLE_NAME, null, values);
if (newRowId != -1) {
Toast.makeText(EntryActivity.this, "Data saved successfully", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(EntryActivity.this, "Error occurred while saving data", Toast.LENGTH_SHORT).show();
}
db.close();
}
}
SQLite 사용법 복잡하긴하네요.
그렇다면 SQLite는 데이터를 저장하는 것 같은데 정확하게 어떤 것일까요?
Q2. SQLite에 대해 설명해줘.
SQLite는 경량의 서버리스 관계형 데이터베이스 관리 시스템(RDBMS)입니다.
이 한줄에 모든게 나와있네요.
SQLite는 소규모 데이터를 저장하고 있네요.
그리고 서버와 통신하여 데이터를 주고 받지 않고 디바이스 저장소안에 데이터를 저장하고 있고요.
또 관계형 데이터를 베이스를 사용하여 Table속에 컬럼으로 이루어진 데이터를
SQL쿼리문(SELECT, INSERT, UPDATE, DELETE)으로 데이터베이스에서 다양한 작업을 수행할 수 있습니다.
그렇다면 이제 SQLite를 더 쉽게 사용할 수 있는 Room라이브러리에 대해서 알아봅시다.
Q3. Room 라이브러리에 대해 설명해줘.
Room 라이브러리는 세 가지 주요 구성 요소로 구성됩니다.
1. Dao(Data Access Object)
: 데이터를 삽입, 업데이트, 삭제 및 선택하는 쿼리를 통해 DB에 엑세스할 수 있습니다.
2. Entity
: 데이터 베이스의 테이블. 테이블의 필드와 데이터를 엑세스 및 수정한다
3. Database
: 데이터베이스 생성, 업데이트 그리고 액세스를 관리하는 역할이다.
위 내용들은 관계형 데이터 베이스를 noSQL로 설계할 때 사용하는 단어들입니다.
위에 내용이 정확하게 이해되지 않겠지만 아래 느낌 정도로 이해하고 있습니다.
- DataBase는 한 저장소안에 User테이블도 있고, Info테이블도 있고, post테이블도 있는 전체 묶음입니다.
- Entity는 User테이블이면 사용자 그 안에는 고유id, 이메일, 패스워드, 주소 같은 정보들이 담습니다.
- DAO는 7번 User는 이메일: "android@lab.com", 비밀번호는 "a1234", 주소는 "seoul"같은 정보를 넣을 수 있게하는 명령문 또는 방금 넣은 7번의 이메일은 뭐야? 같은 질문을 할 수 있습니다.
Q4. Room 라이브러리 어떻게 사용해?
implementation 'androidx.room:room-runtime:$room_version'
kapt 'androidx.room:room-compiler:$room_version'
최신 버전: https://developer.android.com/training/data-storage/room
첫번째로 Entity정의합니다.
@Entity(tableName = "users") data class User( @PrimaryKey @ColumnInfo(name = "user_id") @NonNull val id: String, @ColumnInfo(name = "name") val name: String )
@Entity는 테이블 설계도와 같습니다.
아직은 데이터가 들어가있지는 않지만
지금 이 테이블은 users라는 이름을 가지고 있고
id라는 공간과, name이라는 공간을 가지고 있습니다.
두번째로 Dao를 생성합니다.
@Dao interface UserDao { @Insert suspend fun insertUser(user: User) @Update suspend fun updateUser(user: User) @Delete suspend fun deleteUser(user: User) @Query("SELECT * FROM users WHERE id = :userId") suspend fun getUserById(userId: String): User? @Query("SELECT * FROM users") suspend fun getAllUsers(): List<User> }
@DAO 메서드를 사용해서
이에서 만들어준 Entity설계도에 따라 데이터를 채울 수 있습니다.(@Insert)
또 채운 데이터를 위와 같이 쿼리문을 사용해서 전체를 불러올 수 도 있고(@Query("SELECT * FROM users"))
이렇게 엑세스하는 방법에는
@Insert, @Update, @Delete이 있고
@Query를 통해서 원하는 데이터에 대한 정보를 부분 또는 전체를 불러올 수 있습니다.
세번째로 DataBase 파일을 생성합니다.
@Database(entities = [User::class], version = 1) abstract class MyAppDatabase : RoomDatabase() { abstract fun userDao(): UserDao }
추상화된 클래스를 생성하여 RoomDatabase()로 확장합니다.
위에서 생성한 User클래스로 생성한 Entity를 연결하고
그리고 위에서 생성한 userDAO인터페이스를 사용할 것임을 선언합니다.
네번째로 데이터베이스 초기화합니다.
class MyApplication : Application() { companion object { private var database: MyAppDatabase? = null fun getDatabase(context: Context): MyAppDatabase { if (database == null) { database = Room.databaseBuilder(context, MyAppDatabase::class.java, "my-database") .build() } return database!! } } }
Room.databaseBuilder() 메서드를 사용하여
위에서 생성한 MyAppDatabase의 데이터베이스 인스턴스를 생성합니다.
이 작업은 일반적으로 애플리케이션 인스턴스당 한 번 수행해야 합니다.
마지막으로 Room을 호출합니다.
val coroutineScope = CoroutineScope(Dispatchers.Main) // Create a CoroutineScope val database = MyApplication.getDatabase(this) val userDao = database.userDao() // Perform database operations within a coroutine coroutineScope.launch { // Access the DAO and call suspend functions val userDao = database.userDao() // Insert a user val user = User("1", "John Doe") userDao.insertUser(user) // Retrieve all users val users = userDao.getAllUsers() // Process the retrieved data // ... }
MyApplication클래스의 데이터베이스 생성 함수를 사용하여 인스턴스를 만들어
코루틴을 사용하여 비동기로 데이터베이스에 엑세스합니다.
이게 SQLite를 간단하고 사용하기 쉽게 처리해놓은거라니..
선행학습되어야할 기본 데이터베이스 지식들과 noSQL에 대한 이해
만들어야할 파일들도 너무 많네요.
하지만 간단하게 정리해보면
1. Entity를 만들어서 테이블에는 어떤 정보가 담기는지 설계를 한 후
2. DAO 인터페이스를 만들어 Entity의 데이터에 엑세스할 수 있게 필요한 쿼리문들을 작성합다.
3. Database클래스를 생성하여 위에서 생성한 Entity와 DAO를 연결한 후
4. MyApplication에서 만들어둔 Database클래스를 인스턴스로 반환할 수 있게 만들어놓는 초기화 작업을 합니다.
5. 그리고 원하는 곳에서 Database 인스턴스를 생성해 비동기로 데이터베이스에 엑세스합니다.
Q5. ViewModel에서는 어떻게 Room을 호출하지?
class MyViewModel(application: Application) : AndroidViewModel(application) {
private val userDao: UserDao
init {
val database = MyAppDatabase.getInstance(application)
userDao = database.userDao()
}
fun insertUser(user: User) {
viewModelScope.launch {
userDao.insertUser(user)
}
}
fun updateUser(user: User) {
viewModelScope.launch {
userDao.updateUser(user)
}
}
fun deleteUser(user: User) {
viewModelScope.launch {
userDao.deleteUser(user)
}
}
fun getUserById(userId: String): LiveData<User?> {
return liveData {
val user = withContext(Dispatchers.IO) {
userDao.getUserById(userId)
}
emit(user)
}
}
fun getAllUsers(): LiveData<List<User>> {
return liveData {
val users = withContext(Dispatchers.IO) {
userDao.getAllUsers()
}
emit(users)
}
}
}
viewModel에 application을 파라미터를 받아서 가져오고 있습니다.
또 init함수안에서 데이터베이스를 생성해주고 있고요.
그리고 DAO로 데이터베이스에 엑세스할 때 모두 coroutineScrope가 아닌 viewModelScope를 사용하고 있네요.
마지막으로 특이한 점을 보면 getUserById()와 getAllUsers()를 사용할 때는 withContext(Dispatchers.IO)로
쓰레드를 바꿔주고 있는데 데이터를 호출하면서 users변수안에 데이터를 매핑해주고 있기때문에
LiveData나 Databinding으로 UI가 업데이트 되기 때문입니다.
그리고 emit()함수를 사용해 관찰중인 liveData에게 값을 전송합니다.
오늘 이렇게 준비한 MVVM패턴에서 6가지 안드로이드 아키텍처 컴포넌트들을 사용하는 법들을 배워보았습니다.
ViewModel, LiveData, Databinding, LifeCycle, Paging, Room
이 외에도 Navigation, workmanager, Security같은 컴포넌트들이 있습니다.
다음 챕터3을 모두 끝내고 위 라이브러리들에서도 알아보겠습니다.
그럼 감사합니다.
'안드로이드 연구소 > MVVM+AAC' 카테고리의 다른 글
[MVVM만들기] Paging3 (0) | 2023.05.15 |
---|---|
[MVVM만들기] Lifecycle (1) | 2023.05.15 |
[MVVM만들기] http통신(Retrofit2) (0) | 2023.05.12 |
[MVVM만들기] DataBinding (0) | 2023.05.12 |
[MVVM만들기] ViewModel + LiveData (0) | 2023.05.12 |