채널프래그먼트에서도 영상을 클릭하면 영상 디테일 액티비티 페이지로 넘어가야한다.
[트러블슈팅] 디테일 액티비티 페이지로 넘어갈 때에는 intent에 해당 영상의SearchItem 정보를 담아서 넘겨야하기 때문에 클릭한 아이템의 정보를 받아와야하는데, 나는 간단하게 어댑터 상에서 바로 정보를 받을 수 있도록 만들고 싶었다.
=> 그래서 ChannelVideoAdapter에서 받아온 itemList를 다시 반환해주는 함수를 만들어서, 채널프래그먼트에서 아이템을 클릭했을 때 그 리스트의 position에 해당하는 아이템을 특정하여 인텐트로 넘겨줄 수 있게 되었다.
//ChannelVideoAdapter.kt
fun getItemList(): List<SearchItem> {
return itemList
}
//ChannelFragment.kt
private fun getDetail() {
videoAdapter.itemClick = object : ChannelVideoAdapter.ItemClick {
override fun onClick(view: View, position: Int) {
val intent = Intent(context, DetailActivity::class.java)
intent.putExtra("dataFromFrag", videoAdapter.getItemList()[position] )
Log.d("채널프래그먼트디테일검사","dataItem=${videoAdapter.getItemList()[position]}")
startActivity(intent)
}
}
}
private fun setVideoAdater() {
channelViewModel.channelVideo.observe(viewLifecycleOwner, Observer {
videoAdapter = ChannelVideoAdapter(it)
binding.rvVideo.adapter = videoAdapter
binding.rvVideo.layoutManager = LinearLayoutManager(context)
getDetail()
})
}
[트러블슈팅] getDetail()함수를 처음에 onViewCreated에 setVideoAdater()와 나란히 걸어주었더니 kotlin.UninitializedPropertyAccessException: lateinit property videoAdapter has not been initialized 오류가 나왔다. .. ㅎㅎ 이 문장 그대로 검색해보니 비디오 어댑터가 초기화 되기도 전에 getDetail이 불러와지는게 문제였다. getItemList()때문인가?
=> 그래서 위의 코드 처럼 setVideoAdater()안에 코드를 걸어주니 정상작동 되었다.
디테일 페이지에서도 구독이 가능하게 구현을 하는 것이 목표이기 때문에, 채널 프래그먼트에서 구독 기능을 구현한 내가 디테일 페이지의 구독도 맡게 되었다.
디테일 페이지는 디테일 액티비티로, 각 채널에서 intent를 통해 값을 전달하여 페이지를 띄우는데, 이 때 intent로 전달되는 것은 비디오의 SearchItem 이기 때문에 채널 정보는 전혀 들어가지 않는다.
[트러블슈팅] 채널 정보를 디테일 페이지에 어떻게 가지고 오느냐 자체를 가지고 많이 헤맸다. 단순히 ChannelViewModel을 연결하는 것 만으로는 채널 정보를 가지고 올 수 없어서, channelViewModel.channelList.observe를 걸어도 null이 떴다.
=> 그래서 디테일 액티비티에 한 번 더 setChannelList()를 걸어주었다.
//DetailActivity.kt
private fun setChannelList() {
val pref = requireContext().getSharedPreferences(Constants.CHANNEL_PREFERENCE_KEY, 0)
val channelIds = pref.all.keys
//ChannelItem 데이터 클래스 형식에 맞도록 직접 데이터들을 작성하여 add해주었다.
channelList.add(ChannelItem(val logo: Int,val title: String,val channelId: String,var isSubscribed: Boolean))
for (channelItem in channelList) {
if (channelIds.contains(channelItem.channelId)) {
channelItem.isSubscribed = true
}
}
channelViewModel.addChannelList(channelList)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setChannelList()
channelViewModel.channelList.observe(this) {
it.forEach { item ->
if (recData?.snippet?.channelId == item.channelId) {
//채널 로고 이미지 바인딩
binding.ivChannelThumbnail.setImageResource(item.logo)
//구독 여부에 따른 아이콘 동기화
if(!item.isSubscribed){
binding.btnSubscribe.text = "구독"
binding.btnSubscribe.setTextColor(Color.WHITE)
binding.btnSubscribe.backgroundTintList = ColorStateList.valueOf(Color.BLACK)
} else {
binding.btnSubscribe.text = "구독중"
binding.btnSubscribe.setTextColor(Color.BLACK)
binding.btnSubscribe.backgroundTintList = ColorStateList.valueOf(Color.YELLOW)
}
}
}
}
채널 정보를 세팅해준 다음에는 구독하지 않은 채널의 구독 버튼을 눌렀을 때 즉, isSubscribed = false인 경우에 isSubscribed를 true로 바꿔주고, 아이콘의 상태가 바뀌고, channelViewModel의 _subscriptionList에 들어갈 수 있도록 add 해주고, SharedPreference에도 저장하는 (반대의 경우에는 반대로 될 수 있도록 하는) 함수를 만들어 주었다.
//DetailActivity.kt
binding.btnSubscribe.setOnClickListener {
Log.d("버튼검사", "${channelViewModel.channelList.value}")
channelViewModel.channelList.observe(this) { channelList ->
channelList.forEach { item ->
if (recData?.snippet?.channelId == item.channelId) {
if (!item.isSubscribed) {
item.isSubscribed = true
binding.btnSubscribe.text = "구독중"
channelViewModel.addSubscription(item)
saveChannel(item)
Log.d("버튼구독검사", item.channelId)
} else {
item.isSubscribed = false
binding.btnSubscribe.text = "구독"
channelViewModel.removeSubscription(recData.snippet.channelId)
removeChannel(item)
Log.d("버튼구독취소검사", recData.snippet.channelId)
}
}
}
}
}
[트러블슈팅] 그래도 역시 액티비티와 프래그먼트 간의 영역 차이..는 어쩔 수 없는지 동기화가 바로 안되는 문제가 있었다. 예를 들어서 다이얼로그 프래그먼트에는 구독 중인데, 구독중이라고 뜨지 않다던가..하는
=> 관련 함수들에 로그를 찍어본 결과 channelViewModel에서 addSubscription 함수에 _subscriptionList 뿐 만 아니라 _channelList도 변경해서 그 변경사항을 공유 할 수 있도록 코드를 추가해줬다.
//ChannelViewModel.kt
fun addSubscription(channelData: ChannelListItem) {
val distinctList = _subscriptionList.value?.toMutableList()?.apply {
if (!contains(channelData)) {
add(0,channelData)
}
}
_subscriptionList.value = distinctList?.distinct() ?: emptyList()
//채널리스트에서도 값을 바꿔서 변경사항 전달
_channelList.value?.forEach {
if(it.channelId == channelData.channelId){
it.isSubscribed = true
}
}
}
[트러블슈팅] 구독 추가 하는 건 위의 코드로 해결이 되었는데, 취소는 전혀 동기화가 되지 않았다. channelViewModel의 removeSubscription 함수에도 위에 처럼 _channelList가 변경 값을 인지하도록 만들어도 전혀 소용이 없었다. 심지어는 이렇게 인지하도록 만든 후에 어쩔때에는 되었다가 안되었다가, 디테일 페이지에서 아예 전달하는 channelViewModel.removeSubscription(recData.snippet.channelId)의 recData.snippet.channelId 로그 값이 프래그먼트를 옮긴 후 다시 들어가서 눌러보면 null 로 찍히기도 했다.
=> 솔직히 아직도 문제가 뭔지는 모르겠는데, 한가지 확실한 것은 SharedPreference에는 칼같이 저장되고 칼같이 삭제가 된다는 것이다. 그래서 여기에 착안해서 아예 채널프래그먼트에서 구독 채널 리스트를 매번 SharedPreference를 조회하여 새롭게 불러오는 방식으로 바꾸게 되었다.
//ChannelViewModel.kt
fun clearSubscription() {
_subscriptionList.value = emptyList()
}
//ChannelFragment.kt
private fun loadSubscription() {
//비운 후 다시 새롭게 채울 수 있도록 한다.
channelViewModel.clearSubscription()
val pref = requireContext().getSharedPreferences(Constants.CHANNEL_PREFERENCE_KEY, 0)
val allItems: Map<String, *> = pref.all
for ((_, value) in allItems) {
if (value is String) {
val channelData = Gson().fromJson(value, ChannelListItem::class.java)
channelViewModel.addSubscription(channelData)
}
}
getFirstVideo()
}
//화면에 포커스가 돌아올 때마다 갱신한다.
override fun onResume() {
super.onResume()
loadSubscription()
}
getFirstVideo를 걸어 주지 않은 loadSubscription함수를 onViewCreated안에 다 걸어주었더니, 디테일액티비티에서 뒤로가기로 채널프래그먼트에 돌아오면 화면 갱신이 되지 않아서, 어댑터에 갱신 사항이 반영되지 않았다. 리프레시 할 수 있는 함수들 (ex. fragmentManager.beginTransaction())도 하나도 안 먹혀서 고민하다가,onResume 에 걸어주는 방법을 택했다. loadSubscription()만 걸면 비디오가 나오질 않아서, getFirstVideo()도 걸어주었더니 의도한 대로 작동했다.오늘 구현한 코드들로 이번 프로젝트에서 내가 맡은 부분은 끝났다. 채널프래그먼트 및 구독 사항에 관한 것만 담당했는데도 팀원들과의 마감 시간에 겨우 맞출 수 있었다. 좀 불안정? 한 면도 있고, 급해서 그냥 냅다 가져다 박은 코드들도 있고 하지만, 어쩄든 정상 작동이 된다는 점에서 아주 뿌듯하다. 개인과제 때 한 번 했어도 영 쉽지 않아서 많이 고생했는데, 팀원 분들이 잘 이끌어주셔서 무사히 마칠 수 있게 된 것 같다.
코드 구현은 끝났지만, 아직 발표 준비가 남았다. 디자인도 디테일을 다듬어야 해서 팀원분들과 디테일을 좀 더 맍디고 이번에는 다행이도.ㅎ 발표를 직접하지 않아서, 열심히 PPT를 만들어야 한다.
목요일을 대부분 발표준비로 하루를 보낼 것 같다. 금요일이 발표날인데 무사히 끝마칠 수 있도록 해야겠다!