맨땅에 코딩
'학습의 숲' 소소한 개발일지 - 과일 누적 랭킹 본문
FruitshowRepository
🍀 Firebase Realtime Database에서 데이터를 불러와서 이를 LiveData로 변환하는 역할을 한다. 이를 통해 ViewModel에서 이 LiveData를 관찰하여 데이터가 변경될 때마다 UI를 업데이트할 수 있다. 이렇게 하면 데이터를 가져오는 로직과 UI 로직을 분리하여 코드의 가독성과 유지보수성을 향상시킬 수 있다.
package com.example.forestlearning.repository
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.forestlearning.FruitShowData
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
class FruitshowRepository {
// Firebase Realtime Database 레퍼런스 선언
private val db: DatabaseReference = FirebaseDatabase.getInstance().getReference("Users")
fun fetchFruitData(): LiveData<List<FruitShowData>> {
// LiveData 선언. 이 LiveData는 Firebase에서 가져온 과일 데이터를 갖게 됨
val fruitDataList = MutableLiveData<List<FruitShowData>>()
// Firebase Realtime Database에서 데이터를 불러오는 이벤트 리스너 설정
db.addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
// 데이터가 변경되었을 때 호출되는 메소드
val temp = mutableListOf<FruitShowData>()
for (userSnapshot in dataSnapshot.children) {
// 사용자 이름과 과일 개수를 가져옴
val userName = userSnapshot.child("name").getValue(String::class.java) ?: ""
var totalFruits = 0
// 각 날짜별 과일 개수를 더함
val dayFruitSnapshot = userSnapshot.child("Dayfruit")
for (dateSnapshot in dayFruitSnapshot.children) {
for (fruitSnapshot in dateSnapshot.children) {
val fruitNum = fruitSnapshot.getValue(Int::class.java) ?: 0
totalFruits += fruitNum
}
}
// 가져온 데이터를 FruitShowData 객체로 만들어 temp에 추가
val item = FruitShowData(userName, totalFruits)
temp.add(item)
}
// 과일 개수를 기준으로 데이터를 정렬하고 _fruitData에 추가
temp.sortByDescending { it.fruitnum }
fruitDataList.value = temp
}
override fun onCancelled(databaseError: DatabaseError) {
// 데이터를 가져오는데 실패했을 때 처리
}
})
return fruitDataList
}
}
- Firebase Realtime Database 레퍼런스 선언: Firebase Realtime Database는 클라우드 호스팅 NoSQL 데이터베이스로, 데이터를 JSON 형식으로 저장하고 모든 클라이언트에게 실시간으로 데이터를 제공한다. 여기서는 "Users"라는 노드에 대한 참조를 가져온다.
- LiveData 선언: LiveData는 앱의 생명주기를 인식하는 데이터 홀더 클래스다. LiveData는 액티비티, 프래그먼트, 서비스의 생명주기 상태를 인식하고, 생명주기가 활성 상태인 컴포넌트에만 업데이트를 알린다. 여기서는 과일 데이터를 가지고 있는 LiveData를 선언하고 있다.
- 데이터 가져오기: fetchFruitData() 메소드에서는 Firebase Realtime Database에서 데이터를 불러온다. addValueEventListener를 사용하여 데이터변경사항을 실시간으로 감지하고, 변경사항이 감지되면 onDataChange() 메소드가 호출된다.
- 데이터 처리: onDataChange() 메소드에서는 DataSnapshot 객체를 통해 Firebase의 데이터를 읽는다. DataSnapshot은 데이터베이스의 특정 시점의 데이터 상태를 포함한다. 여기서는 "Users" 노드의 모든 자식노드를 순회하며, 각 사용자의 이름과 과일의 개수를 가져온다. 그리고 이 정보를 FruitShowData 객체로 만들어서 리스트에 추가한다.
- 데이터 정렬 및 LiveData 업데이트: 모든 데이터를 가져와서 FruitShowData 객체로 만든 후에는 과일의 개수를 기준으로 리스트를 정렬한다. 그 후, 이 리스트를 _fruitData에 설정하여 LiveData의 값을 업데이트한다. 이렇게 하면 LiveData를 관찰하고 있는 모든 옵저버에게 알림이 전달되어 UI를 업데이트할 수 있다.
- 오류 처리: onCancelled() 메소드에서는 데이터를 가져오는 도중 오류가 발생했을 때의 처리를 작성할 수 있다. 현재는 아무런 처리도 하지 않고 있다.
FruitshowViewModel
🍀 FruitshowRepository를 통해 Firebase Realtime Database에서 데이터를 가져와 이를 LiveData로 제공한다. 이를 통해 View에서는 LiveData를 관찰하여 데이터가 변경될 때마다 UI를 업데이트할 수 있다. 이렇게 함으로써 데이터를 가져오는 로직과 UI 로직을 분리하여 코드의 가독성과 유지보수성을 향상시킬 수 있다.
package com.example.forestlearning.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.forestlearning.FruitShowData
import com.example.forestlearning.repository.FruitshowRepository
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
class FruitshowViewModel : ViewModel() {
// FruitshowRepository 인스턴스를 생성하여 repository 변수에 저장
private val repository = FruitshowRepository()
// repository를 통해 과일 데이터를 가져와 LiveData 형태로 저장
// 과일 데이터가 변경될 때마다 자동으로 업데이트
val fruitData: LiveData<List<FruitShowData>> = repository.fetchFruitData()
}
- Repository 인스턴스 생성: FruitshowViewModel 클래스 내에서 FruitshowRepository의 인스턴스를 생성한다. FruitshowRepository는 데이터를 가져오는 로직을 담당하며, 이를 ViewModel에서 사용하게 된다. 이렇게 함으로써 데이터를 가져오는 로직과 UI 로직을 분리하여 코드의 가독성과 유지보수성을 향상시킬 수 있다.
- LiveData 가져오기: fruitData는 FruitshowRepository에서 가져온 LiveData를 참조한다. LiveData는 변경 가능한 데이터를 보유하고 있는 데이터 홀더 클래스로, LiveData 객체에 저장된 데이터가 변경되면 구독자에게 알림을 보낸다. 이를 통해 UI를 최신 상태로 유지할 수 있다. 여기서는 FruitshowRepository에서 Firebase Realtime Database에서 가져온 데이터를 LiveData로 변환한 것을 참조하게 된다.
FruitshowFragment
🍀 과일 데이터를 보여주는 화면을 구성하고, 사용자의 검색 요청을 처리하는 역할을 한다. 이를 통해 사용자는 과일 데이터를 오름차순으로 확인하고 사용자 이름을 검샐할 수 있다. 이 클래스는 ViewModel을 통해 데이터를 가져오며, 이 데이터를 RecyclerView에 표시한다. 이렇게 함으로써 UI 로직과 데이터 처리 로직을 분리하여 코드의 가독성과 유지보수성을 향상시킬 수 있다.
package com.example.forestlearning
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.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.forestlearning.databinding.FragmentFruitshowBinding
import com.example.forestlearning.viewmodel.FruitshowViewModel
class FruitshowFragment : Fragment() {
// 바인딩 객체. UI 컴포넌트에 접근할 수 있게 함
var binding: FragmentFruitshowBinding? = null
// ViewModel 객체. UI에서 사용할 데이터를 제공
private val viewModel: FruitshowViewModel by viewModels()
// 뷰를 생성하는 메소드
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 바인딩 객체를 초기화하고 루트 뷰를 반환
binding = FragmentFruitshowBinding.inflate(inflater, container, false)
return binding?.root
}
// 뷰가 생성된 후 호출되는 메소드
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// RecyclerView의 Adapter를 생성하고 설정
val adapter = FruitshowAdapter()
binding?.fruitShowRecycler?.apply {
layoutManager = LinearLayoutManager(context)
this.adapter = adapter
}
// ViewModel의 fruitData를 관찰하여 데이터가 변경될 때마다 Adapter에 반영
viewModel.fruitData.observe(viewLifecycleOwner) { data ->
adapter.fruitData = data
}
// 검색 버튼의 클릭 이벤트를 설정
binding?.searchBtn?.setOnClickListener {
val searchName = binding?.searchName?.text.toString()
if (searchName.isNotEmpty()) {
// 검색어가 비어있지 않을 경우 검색 수행
adapter.search(searchName)
} else {
// 검색어가 비어있을 경우 전체 목록 출력
adapter.search("")
}
}
}
// 뷰가 파괴될 때 호출되는 메소드
override fun onDestroyView() {
super.onDestroyView()
// 뷰가 파괴될 때 바인딩 객체를 null로 설정
binding = null
}
}
- 바인딩 객체 선언: binding 객체는 FragmentFruitshowBinding 클래스의 인스턴스입니다. 이 객체를 통해 뷰에 선언된 UI 컴포넌트에 접근할 수 있다.
- ViewModel 객체 선언: viewModel 객체는 FruitshowViewModel 클래스의 인스턴스입니다. 이 객체를 통해 UI에 필요한 데이터를 가져온다.
- onCreateView 메소드: 이 메소드에서는 Fragment의 뷰를 생성한다. FragmentFruitshowBinding.inflate() 메소드를 통해 바인딩 객체를 초기화하고 뷰를 생성한다.
- onViewCreated 메소드: 이 메소드에서는 뷰가 생성된 후 필요한 설정한다. RecyclerView에 LayoutManager와 Adapter를 설정하며, ViewModel의 LiveData를 관찰하여 데이터가 변경될 때마다 Adapter에 반영한다. 또한, 검색 버튼의 클릭 이벤트를 설정한다.
- onDestroyView 메소드: 이 메소드에서는 Fragment의 뷰가 파괴될 때 필요한 정리 작업을 합니다. 바인딩 객체를 null로 설정하여 메모리 누수를 방지한다.
FruitshowAdapter
🍀 RecyclerView에서 사용하는 데이터와 아이템 뷰를 연결하는 역할을 한다. 이 클래스는 원본 데이터와 검색 결과 데이터를 관리하며, 검색 기능을 제공한다. 이를 통해 사용자는 원하는 데이터만 RecyclerView에서 볼 수 있다. 이 클래스는 ViewHolder를 통해 아이템 뷰를 재사용하여 메모리 사용량을 최적화하고, 스크롤 성능을 향상시킨다.
package com.example.forestlearning
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.forestlearning.databinding.FruitshowListBinding
class FruitshowAdapter : RecyclerView.Adapter<FruitshowAdapter.Holder>() {
private var _fruitData = listOf<FruitShowData>()
var fruitData: List<FruitShowData>
get() = _fruitData
set(value) {
_fruitData = value
filteredData = _fruitData
notifyDataSetChanged()
}
private var filteredData = listOf<FruitShowData>()
// 검색 기능 함수
fun search(searchName: String) {
// 검색어가 포함된 데이터만 filteredData에 추가
filteredData = if (searchName.isEmpty()) {
_fruitData
} else {
_fruitData.filter { it.nickname?.contains(searchName) == true }
}
notifyDataSetChanged()
}
// ViewHolder 클래스
class Holder(val binding: FruitshowListBinding) : RecyclerView.ViewHolder(binding.root) {
fun dataBind(data: FruitShowData, position: Int) {
binding.apply {
userName.text = data.nickname
userFruit.text = data.fruitnum.toString()
userRank.text = (position+1).toString()
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
// ViewHolder를 생성하는 메소드
val binding = FruitshowListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return Holder(binding)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
// ViewHolder에 데이터를 바인딩하는 메소드
holder.dataBind(filteredData[position], position)
}
override fun getItemCount() = filteredData.size
// 아이템 개수를 반환하는 메소드
}
- 데이터 선언: _fruitData와 filteredData 두 개의 데이터 리스트를 선언한다. _fruitData는 원본 데이터를 저장하고, filteredData는 검색 결과를 저장한다. fruitData 프로퍼티를 통해 외부에서 _fruitData에 접근할 수 있게 하였다.
- 검색 메소드: search() 메소드에서는 검색어를 인자로 받아, 검색어가 포함된 데이터만 filteredData에 저장한다. 검색어가 비어있는 경우에는 원본 데이터를 filteredData에 저장한다.
- ViewHolder 클래스: Holder 클래스는 RecyclerView.ViewHolder를 상속받아 생성되었다. 이 클래스에서는 아이템 뷰에 데이터를 바인딩하는 dataBind() 메소드를 정의하고 있다.
- onCreateViewHolder 메소드: 이 메소드에서는 ViewHolder를 생성한다. FruitshowListBinding.inflate() 메소드를 통해 바인딩 객체를 생성하고, 이를 Holder의 인자로 전달하여 Holder를 생성한다.
- onBindViewHolder 메소드: 이 메소드에서는 ViewHolder에 데이터를 바인딩한다. dataBind() 메소드를 호출하여 ViewHolder의 아이템 뷰에 데이터를 설정한다.
- getItemCount 메소드: 이 메소드에서는 아이템의 개수를 반환한다. filteredData의 크기를 반환하여 현재 보여줄 데이터의 개수를 알려준다.
- notifyDataSetChaged() 메소드: RecyclerView.Adapter에서 데이터 세트가 변경되었음을 알리는 데 사용된다. 이 메소드를 호출하면 RecyclerView는 전체 데이터 세트에 대해 뷰를 다시 바인딩하고 그리는 작업을 실행한다. 여기서 notifyDataSetChanged()는 두 가지 상황에서 사용된다.
- fruitData의 setter에서: fruitData가 새로 설정될 때마다 원본 데이터 _fruitData와 필터링된 데이터 filteredData를 업데이트하고, notifyDataSetChanged()를 호출하여 RecyclerView에 데이터 변경을 알린다.
- search() 메소드에서: 검색어에 따라 filteredData가 업데이트되면, notifyDataSetChanged()를 호출하여 RecyclerView에 데이터 변경을 알린다.
'KAU 2023 (2학년) > 객체지향프로그래밍' 카테고리의 다른 글
'학습의 숲' 소소한 개발일지 - 데이터 (0) | 2024.07.25 |
---|---|
'학습의 숲' 소소한 개발일지 - 나의 시간표 (0) | 2024.07.25 |
'학습의 숲' 소소한 개발일지 - 회원 가입, 로그인, 로그아웃 (0) | 2024.07.25 |
'학습의 숲' 소소한 개발일지 - 인트로 (0) | 2024.07.25 |
객체지향프로그래밍 실화인가 - '학습의 숲' (0) | 2024.07.09 |