일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 안드로이드 라이선스
- android retrofit login
- rxjava hot observable
- 안드로이드 os 구조
- 플러터 설치 2022
- 클래스
- ar vr 차이
- 서비스 쓰레드 차이
- android ar 개발
- jvm 작동 원리
- 스택 큐 차이
- rxjava disposable
- 안드로이드 라이선스 종류
- 2022 플러터 설치
- 서비스 vs 쓰레드
- 안드로이드 유닛테스트란
- ANR이란
- Rxjava Observable
- 멤버변수
- jvm이란
- 안드로이드 레트로핏 사용법
- 큐 자바 코드
- 객체
- 자바 다형성
- rxjava cold observable
- 안드로이드 유닛 테스트
- 안드로이드 레트로핏 crud
- 안드로이드 유닛 테스트 예시
- 스택 자바 코드
- 2022 플러터 안드로이드 스튜디오
- Today
- Total
나만을 위한 블로그
[Android] Jetpack Navigation, Room DB, Flow 같이 사용하기 - 3 - 본문
지금까지 Room DB 기본 설정을 끝내고 데이터를 추가하는 로직까지 추가했다.
이제 전체 아이템 조회 함수를 통해 DB에 저장된 모든 데이터를 리사이클러뷰에 붙이는 처리를 구현한다. 데이터가 변경되면 바로 알 수 있도록 ListAdapter와 diffcallback을 사용한다.
이전 글들의 링크는 아래에 있다.
https://onlyfor-me-blog.tistory.com/556
https://onlyfor-me-blog.tistory.com/558
먼저 Item의 가격을 달러에 맞춰 $22.00 형태로 표시하는 확장 함수를 Item 클래스에 추가한다.
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.text.NumberFormat
@Entity(tableName = "item")
data class Item(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "name")
val itemName: String,
@ColumnInfo(name = "price")
val itemPrice: Double,
@ColumnInfo(name = "quantity")
val quantityInStock: Int
)
fun Item.getFormattedPrice(): String = NumberFormat.getCurrencyInstance().format(itemPrice)
그리고 ItemListAdapter 클래스를 만들고 아래와 같이 작성한다.
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.kotlinprac.databinding.ItemListItemBinding
import com.example.kotlinprac.room.data.Item
import com.example.kotlinprac.room.data.getFormattedPrice
class ItemListAdapter(
private val onItemClicked: (Item) -> Unit
) : ListAdapter<Item, ItemListAdapter.ItemViewHolder>(diffCallback) {
class ItemViewHolder(private var binding: ItemListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
binding.apply {
itemName.text = item.itemName
itemPrice.text = item.getFormattedPrice()
itemQuantity.text = item.quantityInStock.toString()
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(
ItemListItemBinding.inflate(LayoutInflater.from(parent.context))
)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val current = getItem(position)
holder.itemView.setOnClickListener {
onItemClicked(current)
}
holder.bind(current)
}
companion object {
private val diffCallback = object : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.itemName == newItem.itemName
}
}
}
}
리사이클러뷰에 클릭 이벤트를 구현하는 방법은 내가 알기로 4가지 정도 있는데 그 중 하나를 사용하고 있다.
궁금하다면 아래 포스팅을 참고하면 된다.
https://onlyfor-me-blog.tistory.com/525
ItemListAdapter를 만들었다면 이제 사용할 차례다. 먼저 InventoryViewModel에 DB 데이터들을 LiveData 형태로 저장할 프로퍼티를 만들고, 해당 프로퍼티에 getItems()의 결과를 담는다.
class InventoryViewModel(
private val itemDao: ItemDao
): ViewModel() {
val allItems: LiveData<List<Item>> = itemDao.getItems().asLiveData() // 이 줄을 추가
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
}
}
프래그먼트에서 저 값의 변경을 관찰하기 위해 asLiveData()를 써서 Flow를 LiveData로 변환한다.
그리고 ItemListFragment로 이동해서 아래 코드를 작성한다.
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.kotlinprac.R
import com.example.kotlinprac.databinding.ItemListFragmentBinding
class ItemListFragment : Fragment() {
private var _binding: ItemListFragmentBinding? = null
private val binding
get() = _binding!!
private val viewModel: InventoryViewModel by activityViewModels {
InventoryViewModelFactory(
(activity?.application as InventoryApplication).database.itemDao()
)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = ItemListFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter = ItemListAdapter {
val action = ItemListFragmentDirections.actionItemListFragmentToItemDetailFragment(it.id)
this.findNavController().navigate(action)
}
binding.recyclerView.adapter = adapter
viewModel.allItems.observe(this.viewLifecycleOwner) { items ->
items.let {
adapter.submitList(it)
}
}
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
binding.floatingActionButton.setOnClickListener {
val action = ItemListFragmentDirections.actionItemListFragmentToAddItemFragment(
getString(R.string.add_fragment_title)
)
this.findNavController().navigate(action)
}
}
}
ItemListAdapter {} 안에서 클릭 시 네비게이션을 사용해서 화면을 이동시키는 게 보인다.
이렇게 하고 앱을 실행하면 상세 화면으로 이동하고 sell, delete 버튼이 각각 보인다. 아직 화면을 이동시켜서 표시하는 처리만 구현했기 때문에 그 외에 별다른 건 안 보인다.
이제 넘겨받은 id를 통해 특정 아이템의 정보들을 가져오는 처리를 구현한다. InventoryViewModel에 retrieveItem()을 만든다.
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
}
fun retrieveItem(id: Int): LiveData<Item> {
return itemDao.getItem(id).asLiveData()
}
}
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
}
}
retrieveItem() 안에서도 마찬가지로 Flow를 LiveData로 사용하기 위해 asLiveData()를 사용한 게 보인다.
이제 상세 화면의 텍스트뷰에 가져온 값들을 알맞게 표시해야 한다. ItemDetailFragment 파일을 아래와 같이 작성한다.
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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.FragmentItemDetailBinding
import com.example.kotlinprac.room.data.Item
import com.example.kotlinprac.room.data.getFormattedPrice
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class ItemDetailFragment : Fragment() {
private var _binding: FragmentItemDetailBinding? = null
private val binding get() = _binding!!
private val navigationArgs: ItemDetailFragmentArgs by navArgs()
lateinit var item: Item
private val viewModel: InventoryViewModel by activityViewModels {
InventoryViewModelFactory(
(activity?.application as InventoryApplication).database.itemDao()
)
}
private fun bind(item: Item) {
binding.apply {
itemName.text = item.itemName
itemPrice.text = item.getFormattedPrice()
itemCount.text = item.quantityInStock.toString()
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentItemDetailBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val id = navigationArgs.itemId
viewModel.retrieveItem(id).observe(this.viewLifecycleOwner) { selectedItem ->
item = selectedItem
bind(item)
}
}
private fun showConfirmationDialog() {
MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(android.R.string.dialog_alert_title))
.setMessage(getString(R.string.delete_question))
.setCancelable(false)
.setNegativeButton(getString(R.string.no)) { _, _ -> }
.setPositiveButton(getString(R.string.yes)) { _, _ ->
deleteItem()
}
.show()
}
private fun deleteItem() {
viewModel.deleteItem(item)
findNavController().navigateUp()
}
private fun editItem() {
val action = ItemDetailFragmentDirections.actionItemDetailFragmentToAddItemFragment(
getString(R.string.edit_fragment_title), // "Edit Item"
item.id
)
this.findNavController().navigate(action)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
이제 다시 앱을 실행하면 상세화면으로 이동했을 때 이름, 가격, 재고가 표시된다.
'Android' 카테고리의 다른 글
[Android] 앱 시작 시간의 종류 (Cold / Warm / Hot Start) (0) | 2023.01.19 |
---|---|
[Android] Jetpack Navigation, Room DB, Flow 같이 사용하기 - 4 - (0) | 2022.12.13 |
[Android] Jetpack Navigation, Room DB, Flow 같이 사용하기 - 2 - (0) | 2022.12.13 |
[Android] Flow vs LiveData (0) | 2022.12.12 |
[Android] Jetpack Navigation, Room DB, Flow 같이 사용하기 - 1 - (0) | 2022.12.12 |