Intro
- 오늘 팀프로젝트의 구현마무리시간을 가졌다. 주요 구현내용은 ViewPager2를 사용하는 Fragment에서 ViewPager2에 사용되지않은 Fragment에 어떻게 넘어갈까였다.
Home or Search item 클릭시 Detail 페이지로 이동
- 해당기능을 구현하기위해서는 Activity를 거쳐서 데이터를 전달해줘야한다. 그러니까 다음과 같이 전달된다. "HomeFragment" ->"DetailActivity" -> "DetailFragment"
- 위와같이 구현해야하는 이유는 우리 프로젝트에서는 ViewPager2+TabLayout을 사용중인데, ViewPager2에 등록되지않은 새로운 Fragment를 화면에 띄우려면
1) 기존 Fragment의 자식 Fragment로 설정해서 클릭이벤트시 띄운다
2) DetailActivity를 만들고 그 위에 Fragment를 띄운다. 즉 DetailActivity를 띄우면 DetailActivity에서 DetailFragment로 Bundle 데이터와 함께 Fragment를 불러온다. 그렇게 작업한 결과가 아래 코드이다.
class DetailActivity : AppCompatActivity() {
companion object {
const val EXTRA_DETAIL = "extra_detail"
fun newIntent(
context: Context,
item: DetailModel,
) : Intent = Intent(context, DetailActivity::class.java).apply {
putExtra(EXTRA_DETAIL, item)
}
}
private val detailBundle by lazy {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(EXTRA_DETAIL, DetailModel::class.java)
}else {
intent.getParcelableExtra(EXTRA_DETAIL)
}
}
private val binding by lazy {
ActivityDetailBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
initBundle()
}
private fun initBundle() {
detailBundle?.let {
supportFragmentManager.beginTransaction().replace(
R.id.detail_container,
DetailFragment.newInstance(it),
).commit()
}
}
override fun onBackPressed() {
}
}
class DetailFragment : Fragment() {
companion object {
const val BUNDLE_DETAIL = "bundle_detail"
fun newInstance(item: DetailModel): DetailFragment = DetailFragment().apply {
arguments = Bundle().apply {
putParcelable(BUNDLE_DETAIL, item)
}
}
}
private var _binding: FragmentDetailBinding? = null
private val binding get() = _binding!!
private val viewModel: DetailViewModel by viewModels { DetailViewModelFactory() }
private val sharedViewModel: MainSharedViewModel by activityViewModels()
private val searchBundle by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
arguments?.getParcelable(BUNDLE_DETAIL, DetailModel::class.java)
} else {
arguments?.getParcelable(BUNDLE_DETAIL)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentDetailBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
initViewModel()
}
private fun initViewModel() {
with(viewModel) {
detailItem.observe(viewLifecycleOwner, Observer {
sharedViewModel.updateBookmarkItems(it)
sharedViewModel.updateHomeItems(viewModel.detailItem)
onBind(it)
})
}
with(sharedViewModel) {
detailEvent.observe(viewLifecycleOwner, Observer { event ->
when (event) {
is MainSharedEventForDetail.UpdateDetailItem -> {
updateItem(event.item)
}
else -> Unit
}
})
}
}
private fun initView() = with(binding) {
toolbar.setNavigationOnClickListener {
searchBundle?.let { item ->
val intent = Intent().apply {
putExtra(
DetailActivity.EXTRA_DETAIL,
item.copy(
isBookmarked = viewModel.getItemBookmarked()
)
)
}
requireActivity().setResult(Activity.RESULT_OK, intent)
requireActivity().finish()
}
}
detailPlayBtn.setOnClickListener {
detailScrollview.smoothScrollTo(0, detailScrollview.bottom)
}
detailBookmarkBtn.setOnClickListener {
detailBookmarkBtn.isSelected = !detailBookmarkBtn.isSelected
searchBundle?.let { item ->
viewModel.isBookmarkedItem(
item.copy(
isBookmarked = detailBookmarkBtn.isSelected
)
)
}
}
detailShareBtn.setOnClickListener {
activity?.let { context ->
viewModel.detailItem.value?.let { detailItem ->
Util.shareUrl(context, detailItem.imgUrl)
}
}
}
searchBundle?.let {
viewModel.addDetailItem(it)
}
}
private fun updateItem(item: DetailModel) {
viewModel.updateDetailItem(item)
}
private fun onBind(item: DetailModel) = with(binding) {
Glide.with(requireContext())
.load(item.imgUrl)
.into(detailImageImageview)
detailTitle.text = item.title
detailDatetimeTv.text = item.datetime
detailPlayerTitle.text = item.title
detailPlayerDescription.text = item.description
item.id?.let { setPlayer(it) }
}
private fun setPlayer(videoId: String) = with(binding) {
detailPlayer.addYouTubePlayerListener(object :
AbstractYouTubePlayerListener() {
override fun onReady(youTubePlayer: YouTubePlayer) {
super.onReady(youTubePlayer)
youTubePlayer.cueVideo(videoId, 0f)
}
})
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
- 동작은 위 gif와 같이 된다. 구현하면서 내가 놓쳤던부분은 Intent에 데이터를 실어보낸 이후 callback을 받아서 처리해야하는데 그렇게하지않고 새로 Activity를 Intent하게되어 SharedViewModel 데이터가 싹 날라갔었다 허헣... 그래서 아무리 북마크를 쌓아도 한개만 쌓이는 기적을 경험하였다. 해결한 코드는 아래에..!
private val detailLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val item = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
result.data?.getParcelableExtra(
DetailActivity.EXTRA_DETAIL,
DetailModel::class.java
)
} else {
result.data?.getParcelableExtra(DetailActivity.EXTRA_DETAIL)
}
sharedViewModel.updateHomeItems(item)
}
}
toolbar.setNavigationOnClickListener {
searchBundle?.let { item ->
val intent = Intent().apply {
putExtra(
DetailActivity.EXTRA_DETAIL,
item.copy(
isBookmarked = viewModel.getItemBookmarked()
)
)
}
requireActivity().setResult(Activity.RESULT_OK, intent)
requireActivity().finish()
}
}
Outro
- 이제 곧 최종프로젝트를 앞두고있다. 여기에 kt입사지원도 내일까지라 오늘밤에 어떻게든 자소서를 끝내려고하는데 이게 참 허허.. 쉽지않다..! 여기에 MVVM 공부도 있으니..!!!
- 그래도 프로젝트에 구조를 하나씩 쌓아나가는것은 여전히 재미있다. 그리고 이것은 더많은 요구사항을 반영하는데 좋은 구조를 잡는 공부이기도하니 내가 입사할 회사도 나도 모두가 해피한 일이다 :) 오늘도 달리고 내일도 달리고 힘내자!!