# 결과물 미리보기
이제는 더미데이터가 아닌 DB의 데이터를 활용하여 직접 받아온다.
# 구현 사항
- 파이어베이스 연동하여 게시글 받아오기
# 프로젝트와 파이어베이스 연동하기
이번 중고거래앱만들기에서는 파이어베이스의 auth, realtimedatabase, storage가 사용할 것이기 때문에 파이어베이스에서 새로운 프로젝트를 만들고 연결해 주도록 한다.
예전 포스팅에서도 다뤘었기 때문에, 링크로 대체하도록 하겠다. 특히 안드로이드 스튜디오버전이 범블비 이상일때에는 종속성 추가가 조금 다르기 때문에 잘 모르겠다면, 아래글을 꼭 참고 할 수 있도록 하자.
implementation platform('com.google.firebase:firebase-bom:30.0.0')
implementation "com.google.firebase:firebase-database-ktx"
implementation "com.google.firebase:firebase-auth-ktx"
implementation "com.google.firebase:firebase-storage-ktx"
또한 아래 링크에 들어가서, 밑으로 내리다보면 파이어베이스에서 제공하는 기능별로 추가해야하는 종속성 목록이 나와있기때문에 보다 쉽게 찾아 볼 수 있다.
# 파이어베이스와 연동하기
지금까지 해왔던 것과 똑같다는 생가이 들 수 있지만, 액티비티가 아니라 프래그먼트를 이용하게 된다는 점에서 프래그먼트가 가지는 라이프사이클 때문에 리스너를 따로 분리시켜주고, onDestroyedView가 실행될때 해당 리스너를 지워줌으로써, 계속해서 똑같은 프래그 먼트가 중첩되는 현상을 없애야 한다. 이렇게 하게 됨으로써 코드를 분리시켰고, 결과적으로는 동기처리에서 문제가 생겼다. 리스너 두개를 달아서 해결했는데, 그 해결책은 맨 밑 에러란에다가 적어두었다.
첫번째 리스너를 통해서 동기아닌 동기처리를 해준다. articleList.clear()를 하여 중복되는 객체가 쌓이는 것을 방지해야 한다.
articleDB.addListenerForSingleValueEvent(object : ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
articleList.clear()
Log.d(TAG,"addListenerForSingleValueEvent is Called!!")
articleDB.addChildEventListener (mChildListener)
}
override fun onCancelled(error: DatabaseError) {}
})
이번에는 파이어베이스에 객체자체를 넘겨주고 받는다는 가정을 사용했다. 객체는 .getValue안에 모델을 넣어주면 된다.
private val mChildListener = object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
// DB에 article이 추가될때마다 동작하는 리스너
// article 자체를 객체를 통해서 주고받음
val articleModel = snapshot.getValue(ArticleModel::class.java)
Log.d(TAG,"addChildEventListener is Called!!")
articleModel ?: return // null시 반환
articleList.add(articleModel)
articleAdapter.submitList(articleList)
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {}
override fun onChildRemoved(snapshot: DataSnapshot) {}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {}
override fun onCancelled(error: DatabaseError) {}
}
대신 모델에는 컨스트럭트를 통해 기본 생성자를 만들어줘야만 연결이 가능하다.
data class ArticleModel (
val sellerId : String,
val title : String,
val createdAt : Long,
val price : String,
val imageUrl : String
){
constructor() : this("","",0,"","")
}
# 에러
## Make sure to call FirebaseApp.initializeApp(Context)
Make sure to call FirebaseApp.initializeApp(Context) first 가 발생했을 시, build.gradle(Module)단에 아래 플러그인을 추가해주지 않았을 경우 발생한다. 아래 플러그인을 추가해주자.
apply plugin: 'com.google.gms.google-services'
## 게시글이 중복되는 현상
DB에는 한 객체밖에 없지만, onCretedView와 DestroyedView가 호출되면서 리스너만 제거해줬기 때문에, 리스트는 그대로 유지되는 현상때문에 발생하는 버그인 듯 하다. View들이 제거되면서 당연히 List변수도 없어지는 줄 알았지만 아닌 듯 하다.
그래서 리스트를 clear()해주는 방법으로 해결하려 했다. 그렇게 되면 프래그먼트 다지워져 버린다.
그래서 로그를 찍어 봤더니
이렇게 onCreatedView에서 List를 싹 비워주고,
object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
// DB에 article이 추가될때마다 동작하는 리스너
// article 자체를 객체를 통해서 주고받음
val articleModel = snapshot.getValue(ArticleModel::class.java)
Log.d(TAG,"${articleModel}")
articleModel ?: return // null시 반환
articleList.add(articleModel)
articleAdapter.submitList(articleList)
Log.d(TAG,"${articleList.size}")
}
리스너가 받아올때 다시 리스트에 들어가는 것은 정상적으로 찍히게 되는데.. 화면에는 onCreatedView상태만 반영이 된다..
### 반쪽짜리 해결
결국 View를 그려주는 시점과 파이어베이스에서 값을 받아오는 시점이 다르기 때문에 발생하는 버그라고 판단했고, 보통은 코루틴을 사용하여 동기처리를 하라고 하지만, 아직 코루틴을 사용하는 방법을 모르기때문에 리스너를 두개를 달아줘서 해결했다. 즉 파이어베이스와 연결되는 시점에서 리스트를 비워줘야 하기 때문이다.
articleDB.addListenerForSingleValueEvent(object : ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
articleList.clear()
Log.d(TAG,"addListenerForSingleValueEvent is Called!!")
articleDB.addChildEventListener (mChildListener)
}
override fun onCancelled(error: DatabaseError) {}
})
결국 파이어베이스를 두번 연결한다는 단점이 있지만, 코루틴을 모른다는 전제하에서는 좋은 해결책 인거 같다. 결과물은 맨 첫번째에 첨부한 것과 같다.
### 시도해봤지만 역시 삽질..
혹시나 리사이클러뷰가 감지를 못하는건가 싶었지만 역시 아니었다..
articleAdapter.notifyDataSetChanged()
private val mChildListener = object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
// DB에 article이 추가될때마다 동작하는 리스너
// article 자체를 객체를 통해서 주고받음
val articleModel = snapshot.getValue(ArticleModel::class.java)
Log.d(TAG,"addChildEventListener is Called!! now size : ${articleList.size}")
articleModel ?: return // null시 반환
articleList.add(articleModel)
articleAdapter.submitList(articleList)
articleAdapter.notifyDataSetChanged() // 추가
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {}
override fun onChildRemoved(snapshot: DataSnapshot) {}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {}
override fun onCancelled(error: DatabaseError) {}
}
// onViewCreated
articleDB.addChildEventListener(mChildListener)
Log.d(TAG,"after addChildEventListener! now size : ${articleList.size}")
// onDestroyView
override fun onDestroyView() {
super.onDestroyView()
Log.d(TAG,"onDestroyView!!")
articleList.clear()
Log.d(TAG,"list is cleared!! now size : ${articleList.size}")
articleDB.removeEventListener(mChildListener)
}
# 궁금한점
프래그먼트를 처음사용하면서 라이프사이클에서 신경써야할 처 관문을 만나게 됐다.
위 메뉴바는 각각의 프래그 먼트와 연결이 돼 있고, 홈 , 채팅, 내 정보를 클릭할때마다, 라이프사이클이 시작되는거를 알 수 있었는데, 처음에는 onDestroyView만 실행되는 줄 알았는데, onDestroyView -> onDestroy 처럼 완전히 프래그먼트를 죽이는 작업까지 한다. 그렇다면 onDestroyView와 onDestroy의 정확한 차이점을 모르겠다. 왜 onCreate에서 코드를 작성하지 않고, onViewCreate에서 시작하는지 정확히 이해가 되지 않는다. 그래서 간단하게 찾아 봤는데,
라고 한다. 근데 확실히 와닿지가 않는다. 써보면서 천천히 이해해야 할 것 같다.
# 참고
프래그먼트 라이프 사이클