관리 메뉴

나만을 위한 블로그

[Android] Jetpack Navigation, Room DB, Flow 같이 사용하기 - 2 - 본문

Android

[Android] Jetpack Navigation, Room DB, Flow 같이 사용하기 - 2 -

참깨빵위에참깨빵_ 2022. 12. 13. 20:59
728x90
반응형

이전 글에서 Room을 사용하기 위한 기본 준비들을 모두 끝냈으니 이제 Room을 사용하기 위한 뷰모델을 만들고 프래그먼트에 실제로 사용한다. 이전 글은 아래 링크를 타고 이동하면 볼 수 있다.

 

https://onlyfor-me-blog.tistory.com/556

 

[Android] Jetpack Navigation, Room DB, Flow 같이 사용하기 - 1 -

예전에 자바로 같은 내용의 포스팅을 쓴 적이 있다. https://onlyfor-me-blog.tistory.com/290 [Android] Room DB 사용법 21.04.13 - 코드 테스트 후 정상 작동 확인, xml 코드 별 어떤 xml의 코드인지 기재 이번 포스팅

onlyfor-me-blog.tistory.com

 

data 패키지 바깥에 InventoryViewModel 클래스를 만든다. 그리고 ItemDao 객체를 매개변수로 기본 생성자에 전달하게 한다.

 

import androidx.lifecycle.*
import com.example.kotlinprac.room.data.Item
import com.example.kotlinprac.room.data.ItemDao
import kotlinx.coroutines.launch
import kotlin.coroutines.coroutineContext

class InventoryViewModel(
    private val itemDao: ItemDao
): ViewModel() {
    
}

 

그리고 클래스 맨 밑에 InventoryViewModelFactory 클래스를 만든다. 마찬가지로 ItemDao를 기본 생성자로 넘겨야 한다.

그리고 ViewModelProvider.Factory 인터페이스를 구현하고 create()를 재정의한다.

 

import androidx.lifecycle.*
import com.example.kotlinprac.room.data.Item
import com.example.kotlinprac.room.data.ItemDao
import kotlinx.coroutines.launch
import kotlin.coroutines.coroutineContext

class InventoryViewModel(
    private val itemDao: ItemDao
): ViewModel() {
    
}

class InventoryViewModelFactory(
    private val itemDao: ItemDao
): ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        require(modelClass.isAssignableFrom(InventoryViewModel::class.java)) {
            throw IllegalArgumentException("Unknown ViewModel class")
        }

        @Suppress("UNCHECKED_CAST")
        return InventoryViewModel(itemDao) as T
    }
}


이제 AddItemFragment에 이것저것 입력하고 save 버튼을 누르면 Room DB에 내용이 저장되게 한다. 먼저 뷰모델에 insertItem()을 만든다.

 

import androidx.lifecycle.*
import com.example.kotlinprac.room.data.Item
import com.example.kotlinprac.room.data.ItemDao
import kotlinx.coroutines.launch
import kotlin.coroutines.coroutineContext

class InventoryViewModel(
    private val itemDao: ItemDao
): ViewModel() {

    private fun insertItem(item: Item) {
        viewModelScope.launch {
            itemDao.insert(item)
        }
    }
}

class InventoryViewModelFactory(
    private val itemDao: ItemDao
): ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        require(modelClass.isAssignableFrom(InventoryViewModel::class.java)) {
            throw IllegalArgumentException("Unknown ViewModel class")
        }

        @Suppress("UNCHECKED_CAST")
        return InventoryViewModel(itemDao) as T
    }
}

 

insertItem()의 매개변수로 넘길 Item 객체를 만들기 위해 문자열 3개를 가져와서 Item 인스턴스를 반환하는 private 함수를 만든다.

 

class InventoryViewModel(
    private val itemDao: ItemDao
): ViewModel() {

    private fun insertItem(item: Item) {
        viewModelScope.launch {
            itemDao.insert(item)
        }
    }

    private fun getNewItemEntry(itemName: String, itemPrice: String, itemCount: String): Item =
        Item(
            itemName = itemName,
            itemPrice = itemPrice.toDouble(),
            quantityInStock = itemCount.toInt()
        )
}

class InventoryViewModelFactory(
    private val itemDao: ItemDao
): ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        require(modelClass.isAssignableFrom(InventoryViewModel::class.java)) {
            throw IllegalArgumentException("Unknown ViewModel class")
        }

        @Suppress("UNCHECKED_CAST")
        return InventoryViewModel(itemDao) as T
    }
}

 

계속해서 방금 만든 getNewItemEntry()에 넣을 문자열 3개를 가져오는 addNewItem()과 을 만든다. 이 함수의 내부에서 insertItem()을 호출해서 Room DB에 아이템을 넣을 수 있게 하고, 호출은 프래그먼트에서 할 것이다.

 

class InventoryViewModel(
    private val itemDao: ItemDao
): ViewModel() {

    private fun insertItem(item: Item) {
        viewModelScope.launch {
            itemDao.insert(item)
        }
    }

    private fun getNewItemEntry(itemName: String, itemPrice: String, itemCount: String): Item =
        Item(
            itemName = itemName,
            itemPrice = itemPrice.toDouble(),
            quantityInStock = itemCount.toInt()
        )
    
    fun addNewItem(itemName: String, itemPrice: String, itemCount: String) {
        val newItem = getNewItemEntry(itemName, itemPrice, itemCount)
        insertItem(newItem)
    }
    
    fun isEntryValid(itemName: String, itemPrice: String, itemCount: String): Boolean {
        if (itemName.isBlank() || itemPrice.isBlank() || itemCount.isBlank()) {
            return false
        }
        return true
    }
}

class InventoryViewModelFactory(
    private val itemDao: ItemDao
): ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        require(modelClass.isAssignableFrom(InventoryViewModel::class.java)) {
            throw IllegalArgumentException("Unknown ViewModel class")
        }

        @Suppress("UNCHECKED_CAST")
        return InventoryViewModel(itemDao) as T
    }
}

 

이제 AddItemFragment로 이동해서 아이템을 추가할 수 있게 뷰모델 참조 객체를 만들고 함수를 호출한다. 전체 코드는 아래와 같다.

 

import android.content.Context
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.example.kotlinprac.R
import com.example.kotlinprac.databinding.FragmentAddItemBinding
import com.example.kotlinprac.room.data.Item

class AddItemFragment : Fragment() {

    private val navigationArgs: ItemDetailFragmentArgs by navArgs()

    // by activityViewModels {} : 속성 위임을 써서 프래그먼트 전체에서 뷰모델을 공유하게 함
    private val viewModel: InventoryViewModel by activityViewModels {
        InventoryViewModelFactory((activity?.application as InventoryApplication).database.itemDao())
    }
    lateinit var item: Item

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

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentAddItemBinding.inflate(inflater, container, false)
        return binding.root
    }

    private fun isEntryValid(): Boolean {
        return viewModel.isEntryValid(
            binding.itemName.text.toString(),
            binding.itemPrice.text.toString(),
            binding.itemCount.text.toString()
        )
    }

    private fun addNewItem() {
        if (isEntryValid()) {
            viewModel.addNewItem(
                binding.itemName.text.toString(),
                binding.itemPrice.text.toString(),
                binding.itemCount.text.toString()
            )
            val action = AddItemFragmentDirections.actionAddItemFragmentToItemListFragment()
            findNavController().navigate(action)
        }
    }

     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.saveAction.setOnClickListener {
            addNewItem()
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()

        // 키보드 숨기기
        val inputMethodManager = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as
                InputMethodManager
        inputMethodManager.hideSoftInputFromWindow(requireActivity().currentFocus?.windowToken, 0)
        _binding = null
    }

}

 

svae 버튼의 클릭 리스너를 만들고 addNewItem()을 연결한다. 실행해서 아이템을 추가해 본다.

추가됐는지 확인하려면 안드로이드 스튜디오 메뉴 중 View > Tool Windows > App Inspection을 누르고 Database Inspector를 선택한다. 그리고 Live updates 체크박스를 체크하고 다시 데이터를 넣어보면 실시간으로 DB에 데이터가 들어가는 걸 볼 수 있다.

반응형
Comments