Android Jetpack

Android LiveData - 4 (Fragment LifeCycleOwner)

----___<<<<< 2022. 1. 9. 05:00

 안드로이드 LiveData를 보다 보면 LifeCycleOwner라는 개념이 나옵니다.

 

 Fragment를 보면 자주 나오는 LifeCycleOwner라는 것을 알아보겠습니다.

 

 구글 공식문서는 어떻게 설명하는지 보겠습니다.

 

Why use viewLifecycleOwner?


Fragment views get destroyed when a user navigates away from a fragment, even though the fragment itself is not destroyed. This essentially creates two lifecycles, the lifecycle of the fragment, and the lifecycle of the fragment's view. Referring to the fragment's lifecycle instead of the fragment view's lifecycle can cause subtle bugs when updating the fragment's view. Therefore, when setting up observers that affect the fragment's view you should:

1. Set up the observers in onCreateView()

2. Pass in viewLifecycleOwner to observers

 

viewLifecycleOwner를 사용하는 이유는 무엇입니까?


프래그먼트 자체가 파괴되지 않더라도 사용자가 프래그먼트에서 멀어지면 프래그먼트 뷰가 파괴됩니다. 이것은 본질적으로 프래그먼트의 라이프사이클과 프래그먼트 뷰의 라이프사이클이라는 두 가지 라이프사이클을 생성합니다. 조각 보기의 수명 주기 대신 조각의 수명 주기를 참조하면 조각 보기를 업데이트할 때 미묘한 버그가 발생할 수 있습니다. 따라서 조각의 보기에 영향을 주는 관찰자를 설정할 때 다음을 수행해야 합니다.

1. onCreateView()에서 관찰자 설정

2. 관찰자에게 viewLifecycleOwner 전달

 

라고 나오는데, Fragment와, FragmentView의 lifeCycle이 달라서 사용한다고 나오는데, 이 부분에 대해 공식문서를 살펴보면 아래와 같이 나와있습니다.

 

https://developer.android.com/guide/fragments/lifecycle

 

 결국에는 Fragment와 View가 조금 달라서 생기는 문제점을 해결하기 위해서라고 받아들이면 될 것 같습니다.

 

 observer에서 Activity와 조금 다르게, viewLifecycleOwner라고 써주면 됩니다.

 

mainViewModel.liveCount.observe(viewLifecycleOwner, Observer {
    textArea.text = it.toString()
})

 

 전체 코드는 아래와 같습니다.

 

 

 

 아래의 방식으로도 테스트

 

class MainActivity : AppCompatActivity() {

    val manager = supportFragmentManager

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val one = findViewById<Button>(R.id.one)
        val two = findViewById<Button>(R.id.two)

        one.setOnClickListener {
            val transaction1 = manager.beginTransaction()
            val fragment1 = BlankFragment1()
            transaction1.replace(R.id.frameArea, fragment1)
            transaction1.addToBackStack(null)
            transaction1.commit()
        }

        two.setOnClickListener {
            val transaction2 = manager.beginTransaction()
            val fragment2 = BlankFragment2()
            transaction2.replace(R.id.frameArea, fragment2)
            transaction2.addToBackStack(null)
            transaction2.commit()
        }


    }
}
class BlankFragment1 : Fragment() {

    private lateinit var viewModel : BlankViewModel1


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }

    @SuppressLint("FragmentLiveDataObserve")
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View? {

        val view = inflater.inflate(R.layout.fragment_blank1, container, false)

        viewModel = ViewModelProvider(this).get(BlankViewModel1::class.java)

        view.findViewById<Button>(R.id.plus).setOnClickListener {
            viewModel.plusCountBtn()

        }

        viewModel.liveCount.observe(this, Observer {
            Log.d("liveCount", "aa")
            view.findViewById<TextView>(R.id.textArea).text = viewModel.liveCount.value.toString()
        })

        // Inflate the layout for this fragment
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        Log.d("BlankFragment1", "onDestroyView")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("BlankFragment1", "onDestroy")
    }


}

 

 ViewBinding에서도 동일하게 알 수 있습니다.

 

https://yoon-dailylife.tistory.com/57

 

Android) Fragment에서 View Binding 문제점, 제대로 사용하기

View Binding을 모르시는 분들은 이전 글에서 확인 부탁드립니다. Problems in ViewBinding View Binding in Fragment private var _binding: ResultProfileBinding? = null // This property is only valid betw..

yoon-dailylife.tistory.com

 

 

 

 - 참고

 

https://gift123.tistory.com/57

 

안드로이드 개발 (28) ViewLifeCycleOwner

블로그 재작성 시작입니다. jetpack 라이브러리를 사용하다보면 뷰모델 프로바이더를 생성할 때나 라이브 데이타 observe 함수에 매개 변수로 lifeCycleOwner 를 전달해줘야하는 경우가 있습니다. 검색

gift123.tistory.com

 

https://developer.android.google.cn/codelabs/kotlin-android-training-live-data/index.html?index=..%2F..android-kotlin-fundamentals&hl=IT#4 

 

 

 

-- 아래와 같이 작성

 

참고: 프래그먼트는 뷰보다 오래 지속됩니다. 프래그먼트의 onDestroyView() 메서드에서 결합 클래스 인스턴스 참조를 정리해야 합니다.

 

https://developer.android.com/topic/libraries/view-binding

 

뷰 결합  |  Android 개발자  |  Android Developers

뷰 결합 뷰 결합 기능을 사용하면 뷰와 상호작용하는 코드를 쉽게 작성할 수 있습니다. 모듈에서 사용 설정된 뷰 결합은 모듈에 있는 각 XML 레이아웃 파일의 결합 클래스를 생성합니다. 바인딩

developer.android.com

 

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    val manager = supportFragmentManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        binding.btn1.setOnClickListener {
            val transaction1 = manager.beginTransaction()
            val fragment1 = BlankFragment1()
            transaction1.replace(R.id.testArea, fragment1)
            transaction1.addToBackStack(null)
            transaction1.commit()
        }

        binding.btn2.setOnClickListener {
            val transaction2 = manager.beginTransaction()
            val fragment2 = BlankFragment2()
            transaction2.replace(R.id.testArea, fragment2)
            transaction2.addToBackStack(null)
            transaction2.commit()
        }

    }
}

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Button
            android:id="@+id/btn1"
            android:text="btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <Button
            android:id="@+id/btn2"
            android:text="btn2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/testArea"
            android:name="com.bokchi.jetpack_ex.BlankFragment1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </LinearLayout>




</androidx.constraintlayout.widget.ConstraintLayout>

 

class BlankFragment1 : Fragment() {

    private val TAG = "BlankFragment1"

    private var _binding: FragmentBlank1Binding? = null
    private val binding get() = _binding!!

    private lateinit var viewModel: BlankViewModel1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View? {
        // Inflate the layout for this fragment

        _binding = FragmentBlank1Binding.inflate(inflater, container, false)
        val view = binding.root

        viewModel = ViewModelProvider(this).get(BlankViewModel1::class.java)

        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.d(TAG, "onViewCreated")

        binding.btn1.setOnClickListener {
            viewModel.plusCountValue()
        }

        viewModel.liveCount.observe(viewLifecycleOwner, Observer {
            binding.text1.text = it.toString()
        })


    }

    override fun onDestroyView() {
        super.onDestroyView()
        Log.d(TAG, "onDestroyView")
        _binding = null
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy")
    }



}

 

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#34b1eb"
    tools:context=".BlankFragment1">

    <!-- TODO: Update blank fragment layout -->

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/text1"
            android:text="0"
            android:textSize="50sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <Button
            android:id="@+id/btn1"
            android:text="btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    </LinearLayout>


</FrameLayout>

 

 

class BlankViewModel1 : ViewModel() {

    private var _mutableCount = MutableLiveData(0)
    val liveCount : LiveData<Int>
        get() = _mutableCount


    fun plusCountValue() {
        _mutableCount.value = _mutableCount.value?.plus(1)
    }


}