728x90

프래그먼트간 데이터를 공유하려면 여러 가지 방법이있다.

  • ViewModel 사용하기 
    • 여러 프래그먼트가 같은 액티비티에 속해 있으면, 이 프래그먼트들은 같은 ViewModel을 공유해서 데이터를 주고받을 수 있어서 프래그먼트들 사이의 결합도가 낮아지고, 앱을 더 쉽게 관리할 수 있다.
  • 액티비티를 통해 데이터 공유하기
    • 프래그먼트는 액티비티에 붙어 있어서 액티비티를 매개체로 삼아 데이터를 주고받을 수 있다.
    • 한 프래그먼트에서 액티비티의 메소드를 호출해 데이터를 전달하고, 다른 프래그먼트에서는 액티비티를 통해 그 데이터를 가져올 수 있다.
  • 인터페이스 사용하기
    • 데이터를 공유할 때 인터페이스를 정의해서 사용하는 방법.
    • 액티비티에서 인터페이스를 구현하고, 데이터를 보내고 싶은 프래그먼트에서는 인터페이스 메소드를 호출해 데이터를 전달하면 다른 프래그먼트에서 이 인터페이스를 통해 데이터를 받을 수 있다.
    • 복잡하다.
  • Bundle 사용해서 데이터 전달하기
    • 프래그먼트를 전환할 때 Bundle에 데이터를 담아서 보낼 수 있음.
    • 주로 새 프래그먼트를 만들 때 시작 데이터를 전달하는 데 사용됨.
  • EventBus 같은 외부 라이브러리 사용하기
    • EventBus 같은 라이브러리를 사용해서 앱 전체에서 이벤트를 발행하고 구독함으로써 프래그먼트 간에 데이터를 공유할 수 있다.
    • 코드를 더 간결하게 만들어 주고, 결합도를 낮출 수 있지만, 외부 라이브러리에 의존하게 됨.
  • Shared Preferences로 데이터 공유하기
    • 간단한 데이터는 Shared Preferences를 통해 저장하고 공유할 수 있음. 앱의 설정 값이나 작은 데이터 조각을 저장하는 데 주로 사용
    • 매번 sharedPreference에서 값을 읽어와야하는 번거로움.

 

이번 프로젝트에서는 ViewModel을 사용하는 방법을 적용해 보았다.  

 

Activity에 포함된 둘 이상의 프래그먼트는 ViewModel을 통해서 데이터 공유가 가능하다.

 

Fragment들은 각자의 ViewModel (by viewModels()) 를 가질 수도 있고,

Activity의 ViewModel (by activityViewModels)  을 공유해서 사용할 수도 있는 것

 

프로젝트에 적용해 보기

해당 공연의 id를 받아와서 데이터를 뿌려주는 화면

HomeFragment ( TicketDialogFragment 으로 데이터를 보낼 Fragment

더보기
package com.nbc.curtaincall.ui.home

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.tabs.TabLayoutMediator
import com.nbc.curtaincall.R
import com.nbc.curtaincall.databinding.FragmentHomeBinding
import com.nbc.curtaincall.fetch.network.retrofit.RetrofitClient.fetch
import com.nbc.curtaincall.fetch.repository.impl.FetchRepositoryImpl
import com.nbc.curtaincall.ui.home.adapter.GenreAdapter
import com.nbc.curtaincall.ui.home.adapter.KidShowAdapter
import com.nbc.curtaincall.ui.home.adapter.PosterClickListener
import com.nbc.curtaincall.ui.home.adapter.TopRankAdapter
import com.nbc.curtaincall.ui.home.adapter.UpcomingShowAdapter
import com.nbc.curtaincall.ui.main.MainViewModel
import com.nbc.curtaincall.ui.main.MainViewModelFactory
import com.nbc.curtaincall.ui.ticket.TicketDialogFragment
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class HomeFragment : Fragment(), PosterClickListener {
    private var _binding: FragmentHomeBinding? = null
    private val binding get() = _binding!!
    //해당 프레그먼트의 ViewModel
    private val viewModel: HomeViewModel by viewModels {
        HomeViewModelFactory(
            fetchRemoteRepository = FetchRepositoryImpl(fetch),
        )
    }
    //해당 액티비티의 ViewModel -> sharedViewModel을 통해서 데이터 공유
    private val sharedViewModel: MainViewModel by activityViewModels<MainViewModel> {
        MainViewModelFactory(
            fetchRemoteRepository = FetchRepositoryImpl(
                fetch
            )
        )
    }
    private val upComingShowAdapter: UpcomingShowAdapter by lazy { UpcomingShowAdapter() }
    private val topRankAdapter: TopRankAdapter by lazy { TopRankAdapter(this) }
    private val genreAdapter: GenreAdapter by lazy { GenreAdapter(this) }
    private val kidShowAdapter: KidShowAdapter by lazy { KidShowAdapter(this) }
    private var isPaging = false
    private var pagingJob: Job? = null

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        initViews()
        setUpObserve()
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        with(viewModel) {
            //공연 예정작
            fetchUpcoming()
            //TOP 10 공연
            fetchTopRank()
            //장르 스피너 선택
            binding.spinnerHomeGenre.setOnSpinnerItemSelectedListener<String> { _, _, newIndex, _ ->
                fetchGenre(newIndex)
            }
            //어린이 관람 가능 공연 목록
            fetchKidShow()
        }
    }

    //화면 초기 설정
    private fun initViews() {
        //어뎁터 초기화
        upComingShowAdapter
        topRankAdapter
        kidShowAdapter
        initRecyclerView()
        with(viewModel) {
            showList.observe(viewLifecycleOwner) {
                upComingShowAdapter.submitList(it)
                with(binding) {
                    //viewpager 연결
                    viewPager.adapter = upComingShowAdapter
                    //tab 연결
                    TabLayoutMediator(tabPosterIndicator, viewPager) { tab, position ->
                        viewPager.currentItem = tab.position
                    }.attach()
                }
                if (!isPaging) startPaging()
            }
            //장르 연극 초기화
            fetchGenre(0)
        }
    }

    //옵저브 세팅
    private fun setUpObserve() {
        with(viewModel) {
            topRank.observe(viewLifecycleOwner) {
                topRankAdapter.submitList(it.take(10))
            }
            genre.observe(viewLifecycleOwner) {
                genreAdapter.submitList(it)
            }
            kidShow.observe(viewLifecycleOwner) {
                kidShowAdapter.submitList(it)
            }
        }
    }

    //리사이클러뷰 초기화
    private fun initRecyclerView() {
        with(binding) {
            //HOT 추천 리사이클러뷰
            rvHomeTopRank.apply {
                adapter = topRankAdapter
                layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
            }
            //장르별 리사이클러뷰
            rvHomeGenre.apply {
                adapter = genreAdapter
                layoutManager =
                    LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
            }
            //어린이 공연 리사이클러뷰
            rvHomeKidShow.apply {
                adapter = kidShowAdapter
                layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
            }
        }
    }

    //3초 후 자동 페이징
    private fun nextPage() {
        runCatching {
            with(binding) {
                if (viewPager.currentItem == 9) {
                    lifecycleScope.launch {
                        delay(3000)
                    }
                    viewPager.currentItem = 0
                } else {
                    viewPager.currentItem++
                }
            }
        }.onFailure {
            Toast.makeText(context, "Exception nextPage()", Toast.LENGTH_SHORT).show()
        }
    }

    //페이징 스타트 함수
    private fun startPaging() {
        isPaging = true
        pagingJob = lifecycleScope.launch {
            while (true) {
                delay(3000)
                nextPage()
            }
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
        isPaging = false
        pagingJob?.cancel()
    }

    //포스터 클릭 시 티켓
    override fun posterClicked(id: String) {
        val ticketDialog = TicketDialogFragment()
        sharedViewModel.sharedShowId(id) //해당 공연의 id를 MainViewModel로 보내줌
        ticketDialog.setStyle(
            DialogFragment.STYLE_NORMAL,
            R.style.RoundCornerBottomSheetDialogTheme
        )
        ticketDialog.show(childFragmentManager, TicketDialogFragment().tag)
    }
}

 

TicketDialogFragment (HomeFragment 에서 데이터를 받아올 Fragment) 

더보기
package com.nbc.curtaincall.ui.ticket

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import coil.load
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.nbc.curtaincall.databinding.SimpleInfoBottomsheetDialogBinding
import com.nbc.curtaincall.fetch.network.retrofit.RetrofitClient.fetch
import com.nbc.curtaincall.fetch.repository.impl.FetchRepositoryImpl
import com.nbc.curtaincall.ui.main.MainViewModel
import com.nbc.curtaincall.ui.main.MainViewModelFactory

class TicketDialogFragment : BottomSheetDialogFragment() {
    private var _binding: SimpleInfoBottomsheetDialogBinding? = null
    private val binding get() = _binding!!
    //해당 액티비티의 ViewModel -> sharedViewModel을 통해서 데이터 공유
    private val sharedViewModel: MainViewModel by activityViewModels<MainViewModel> {
        MainViewModelFactory(
            fetchRemoteRepository = FetchRepositoryImpl(fetch = fetch)
        )
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = SimpleInfoBottomsheetDialogBinding.inflate(inflater, container, false)

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //TODO Swipe To Action
        with(sharedViewModel) {
            //HomeFragment에서 id를 보내주면 fetchShowDetail() 호출
            showId.observe(viewLifecycleOwner) { id ->
                sharedViewModel.fetchShowDetail(id)
            }
            showDetailInfo.observe(viewLifecycleOwner) { showDetailInfoList ->
                with(binding) {
                    ivDetailPosterImage.load(showDetailInfoList[0].poster)
                    tvDetailAge.text = showDetailInfoList[0].prfage
                    tvDetailRuntime.text = showDetailInfoList[0].prfruntime
                    tvDetailFacilityName.text = showDetailInfoList[0].dtguidance
                    tvDetailPrice.text= showDetailInfoList[0].pcseguidance
                    tvDetailFacilityName.text = showDetailInfoList[0].fcltynm
                    tvDetailGenre.text = showDetailInfoList[0].genrenm
                }
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }
}

HomeViewModel (액티비티의 ViewModel로 MainViewModel 에서 데이터 공유)

더보기
package com.nbc.curtaincall.ui.main

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.nbc.curtaincall.fetch.model.DbResponse
import com.nbc.curtaincall.fetch.repository.impl.FetchRepositoryImpl
import kotlinx.coroutines.launch

class MainViewModel(private val fetchRemoteRepository: FetchRepositoryImpl) : ViewModel() {
    //공연 상세 정보 리스트
    private val _showDetailInfo = MutableLiveData<List<DbResponse>>()
    val showDetailInfo: LiveData<List<DbResponse>> get() = _showDetailInfo
    //공연 id
    private val _showId = MutableLiveData<String>()
    val showId: LiveData<String> get() = _showId

    //id를 공유 하기 위한 함수
    fun sharedShowId(id: String) {
        _showId.value = id
    }

    //공연 상세 정보를 받아옴
    fun fetchShowDetail(id: String) {
        viewModelScope.launch {
            _showDetailInfo.value = fetchRemoteRepository.fetchShowDetail(path = id).showList
        }
    }
}

class MainViewModelFactory(private val fetchRemoteRepository: FetchRepositoryImpl):ViewModelProvider.Factory{
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return MainViewModel(fetchRemoteRepository) as T
    }
}

 

 

HomeFragment 에서 MainViewModel 로 id를 보내주고 TicketDialogFragment 에서 저장된 id 를 꺼내쓰는 구조 

Fragment가 여러개라도 속해있는 Activity에서 파생되었기 때문에 Activity를 통해서 데이터 공유가 가능하다. 

+ Recent posts