본문 바로가기

[Android/Kotlin] 안드로이드 Room Database 사용하기(2) Select...

꿈꾸는블로그왕 2021. 2. 1.

안녕하세요. 오늘 Room Databse를 활용해서 데이터를 가져오는 방법에 대해서 말씀드리겠습니다.

 

지난 시간에는 Room Database에 대한 설명과 간단하게 데이터를 입력하는 방법에 대해서 알아봤습니다.

 

2021/01/30 - [Android/AAC] - [Android/Kotlin] 안드로이드 Room Database 사용하기(1)

 

오늘은 입력한 데이터를 가져오도록 하겠습니다.

 

저장하기 버튼을 누르면 바로 아래 ReclclerView 목록에 가져오도록 합니다.

 

완성된 모습은 아래와 같습니다.

 

프로젝트 구조는 아래와 그림과 같습니다.

 

STEP01. Resource 클래스 만들기

Resource 파일은 sealed class 입니다.

sealed라는 말은 봉인된이라는 의미로 무언가를 봉인하고 있는 것을 말합니다.

Success와 Error를 판단하는 역할을 하고 When문과 함께 유용하게 사용할 수 있습니다.

[Resource.kt]

sealed class Resource<T>(val data: T?, val message: String?) {
    class Success<T>(data: T) : Resource<T>(data, null)
    class Error<T>(message: String) : Resource<T>(null, message)
}

 

STEP02. ExpenseRepository 수정하기

수정전 getAllExpenses() 메소드는 List<Expense>를 넘겨 주었습니다.

하지만 이제는 Resource<List<Expense>> 타입을 리턴하고 있습니다. 데이터베이스 액세스 결과를 Success와 Error로 나누어 리턴해 줍니다.

[ExpenseRepository.kt]

class ExpenseRepository(
    private val expenseDao: ExpenseDao
) {
    // 수정전
    suspend fun getAllExpenses() = expenseDao.getAllExpenses()
    // 수정후
	suspend fun getAllExpenses(): Resource<List<Expense>> {
        return try {
            val response = expenseDao.getAllExpenses()
            Resource.Success(response)
        } catch (e: Exception) {
            Resource.Error(e.message ?: "에러가 발생했습니다.")
        }
    }

    suspend fun insertExpense(expense: Expense) = expenseDao.insertExpense(expense)

    suspend fun updateExpense(expense: Expense) = expenseDao.insertExpense(expense)

    suspend fun deleteExpense(expense: Expense) = expenseDao.insertExpense(expense)
}

 

STEP03. MainViewModel 추가

MainViewModel에서 getAllExpenses() 메소드를 정의해 줍니다.

repository.getAllExpenses() 호출하여 그 결과에 따라서 when문에서 is Resource.Error 또는 is Resource.Success

로 선택적으로 실행 할 수 있습니다.

 

실드 클래스 ExpenseEvent를 만들어 4가지 경우로 리턴값을 정의했습니다. is Resource.Error 시 Failure 에러 값을 담고isResource.Success 시 Success에 Expense List를 담습니다.

 

StateFlow를 통해 상태 변화를 업데이트 해주는 코드를 만들었습니다.

 

Room Database에 데이터를 입력한 후 RecyclerView 새로운 목록을 가져오기 위해 insertExpense 메소드를 수정했습니다. insert후 getAllExpense를 호출하여 상태를 업데이트 합니다.

[MainViewModel.kt]

sealed class ExpenseEvent {
    class Success(val expenseList: List<Expense>): ExpenseEvent()
    class Failure(val message: String): ExpenseEvent()
    object Loading: ExpenseEvent()
    object Empty: ExpenseEvent()
}

private val _expense = MutableStateFlow<ExpenseEvent>(ExpenseEvent.Empty)
val expense: StateFlow<ExpenseEvent> = _expense

...

fun getAllExpenses() =
    viewModelScope.launch(Dispatchers.IO) {
        _expense.value = ExpenseEvent.Loading
        when (val response = repository.getAllExpenses()) {
            is Resource.Error -> _expense.value = ExpenseEvent.Failure(response.message!!)
            is Resource.Success -> _expense.value = ExpenseEvent.Success(response.data!!)
        }
    }
    
fun insertExpense(expense: Expense) =
    viewModelScope.launch(Dispatchers.IO) {
        withContext(Dispatchers.Default) {
            repository.insertExpense(expense)
        }
        _expense.value = ExpenseEvent.Loading
        when (val response = repository.getAllExpenses()) {
            is Resource.Error -> _expense.value = ExpenseEvent.Failure(response.message!!)
            is Resource.Success -> _expense.value = ExpenseEvent.Success(response.data!!)
        }
    }

 

STEP04. MainActivity 추가하기

RecyclerView를 추가하고 MainViewModel expense 상태 변화에 따라 데이터를 업데이트 해주는 코드입니다.

[MainActivity.kt]

binding.rvExpense.apply {
    setHasFixedSize(true)
    layoutManager = LinearLayoutManager(this@MainActivity)
}

lifecycleScope.launchWhenStarted {
    mainViewModel.expense.collect { event ->
        when(event) {
            is MainViewModel.ExpenseEvent.Loading -> {

            }
            is MainViewModel.ExpenseEvent.Success -> {
                expenseAdapter = ExpenseAdapter()
                expenseAdapter.submitList(event.expenseList)
                binding.rvExpense.adapter = expenseAdapter
            }
            is MainViewModel.ExpenseEvent.Failure -> {

            }
            else -> {

            }
        }
    }
}

 

아래는 전체 코드입니다.

 

[MainActivity.kt]

class MainActivity : AppCompatActivity() {

    private var _binding: ActivityMainBinding? = null
    private val binding get() = _binding!!

    private lateinit var mainViewModel: MainViewModel
    private lateinit var expenseAdapter: ExpenseAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        _binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        mainViewModel = ViewModelProvider(this, MainViewModel.Factory(application)).get(MainViewModel::class.java)
        mainViewModel.getAllExpenses()

        binding.rvExpense.apply {
            setHasFixedSize(true)
            layoutManager = LinearLayoutManager(this@MainActivity)
        }

        binding.btnSave.setOnClickListener {
            val expense = Expense().apply {
                amount = binding.edtAmount.text.toString().trim().toInt()
                title = binding.edtTitle.text.toString().trim()
                description = binding.edtDescription.text.toString().trim()
            }

            mainViewModel.insertExpense(expense)
        }

        lifecycleScope.launchWhenStarted {
            mainViewModel.expense.collect { event ->
                when(event) {
                    is MainViewModel.ExpenseEvent.Loading -> {

                    }
                    is MainViewModel.ExpenseEvent.Success -> {
                        expenseAdapter = ExpenseAdapter()
                        expenseAdapter.submitList(event.expenseList)
                        binding.rvExpense.adapter = expenseAdapter
                    }
                    is MainViewModel.ExpenseEvent.Failure -> {

                    }
                    else -> {

                    }
                }
            }
        }
    }


    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }

}

 

[activity_main.xml]

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" >

        <EditText
            android:id="@+id/edt_amount"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Amount"
            android:inputType="number" />

        <EditText
            android:id="@+id/edt_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Title"
            android:inputType="text" />

        <EditText
            android:id="@+id/edt_description"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Description"
            android:inputType="text" />

        <Button
            android:id="@+id/btn_save"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="저장하기" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_expense"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

 

 

댓글