Current scope: simple_xml| all classes
|
kr.open.library.simple_ui.xml.ui.components.fragment.binding
Coverage Summary for Class: BaseDataBindingFragment (kr.open.library.simple_ui.xml.ui.components.fragment.binding)
| Class | Class, % | Method, % | Branch, % | Line, % | Instruction, % |
|---|---|---|---|---|---|
| BaseDataBindingFragment | 100% (1/1) | 80% (4/5) | 80% (8/10) | 85.4% (35/41) |
package kr.open.library.simple_ui.xml.ui.components.fragment.binding
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
/**
* A base Fragment class that uses DataBinding and provides common functionality for data-bound fragments.<br>
* Extends ParentsBindingFragment to provide common binding functionality and permission management.<br><br>
* DataBinding을 사용하고 데이터 바인딩 Fragment에 대한 공통 기능을 제공하는 기본 Fragment 클래스입니다.<br>
* ParentsBindingFragment를 상속받아 공통 바인딩 기능과 권한 관리를 제공합니다.<br>
*
* **Why this class exists / 이 클래스가 필요한 이유:**<br>
* - Android's DataBinding requires manual DataBindingUtil.inflate() and lifecycleOwner setup for each Fragment.<br>
* - This class eliminates boilerplate by accepting a layout resource ID and automatically configuring DataBinding.<br>
* - Enables two-way data binding with ViewModel and automatic UI updates through LiveData observables (StateFlow requires manual collection).<br><br>
* - Android의 DataBinding은 각 Fragment마다 수동으로 DataBindingUtil.inflate() 및 lifecycleOwner 설정이 필요합니다.<br>
* - 이 클래스는 레이아웃 리소스 ID를 받아 자동으로 DataBinding을 구성하여 보일러플레이트를 제거합니다.<br>
* - ViewModel과의 양방향 데이터 바인딩 및 LiveData 옵저버블을 통한 자동 UI 업데이트를 가능하게 합니다 (StateFlow는 수동 수집 필요).<br>
*
* **Design decisions / 설계 결정 이유:**<br>
* - Uses constructor parameter for layout resource ID to enable simple single-line class declaration.<br>
* - Automatically sets lifecycleOwner to viewLifecycleOwner in onViewCreated() for LiveData observation tied to the view lifecycle.<br>
* - Implements final createBinding() to prevent subclasses from breaking the DataBinding initialization contract.<br>
* - Inherits event collection infrastructure from ParentsBindingFragment for consistent ViewModel integration.<br>
* - Properly cleans up binding in onDestroyView() by setting lifecycleOwner to null and calling unbind().<br><br>
* - 간단한 한 줄 클래스 선언을 위해 생성자 파라미터로 레이아웃 리소스 ID를 사용합니다.<br>
* - onViewCreated()에서 뷰 생명주기에 연결된 LiveData 관찰을 위해 lifecycleOwner를 viewLifecycleOwner로 자동 설정합니다.<br>
* - final createBinding()을 구현하여 하위 클래스가 DataBinding 초기화 계약을 깨는 것을 방지합니다.<br>
* - 일관된 ViewModel 통합을 위해 ParentsBindingFragment로부터 이벤트 수집 인프라를 상속받습니다.<br>
* - onDestroyView()에서 lifecycleOwner를 null로 설정하고 unbind()를 호출하여 바인딩을 적절히 정리합니다.<br>
*
* **Usage / 사용법:**<br>
* 1. Extend this class with your Fragment and pass the layout resource ID.<br>
* 2. The lifecycleOwner is automatically set to viewLifecycleOwner in onViewCreated() - no manual setup required for LiveData observation.<br>
* 3. Bind your ViewModel to the binding object in onViewCreated(binding, savedInstanceState) (binding is accessible via getBinding()).<br>
* 4. Use XML data binding expressions to bind views to ViewModel properties.<br>
* 5. Override onEventVmCollect(binding:BINDING) to collect ViewModel events with repeatOnLifecycle().<br><br>
* 1. Fragment에서 이 클래스를 상속받고 레이아웃 리소스 ID를 전달하세요.<br>
* 2. lifecycleOwner는 onViewCreated()에서 viewLifecycleOwner로 자동 설정됨 - LiveData 관찰을 위한 수동 설정이 필요하지 않습니다.<br>
* 3. onViewCreated(binding, savedInstanceState)에서 ViewModel을 binding 객체에 바인딩하세요 (binding은 getBinding()을 통해 접근 가능).<br>
* 4. XML 데이터 바인딩 표현식을 사용하여 뷰를 ViewModel 프로퍼티에 바인딩하세요.<br>
* 5. repeatOnLifecycle()로 ViewModel 이벤트를 수집하려면 onEventVmCollect(binding:BINDING)를 오버라이드하세요.<br>
*
* **Usage example:**<br>
* ```kotlin
* class HomeFragment : BaseDataBindingFragment<FragmentHomeBinding>(R.layout.fragment_home) {
* private val viewModel: HomeViewModel by lazy { getViewModel() }
*
* override fun onViewCreated(binding: FragmentHomeBinding, savedInstanceState: Bundle?) {
* binding.viewModel = viewModel
* // lifecycleOwner is already set automatically - no manual setup needed
* setupViews()
* }
*
* override fun onEventVmCollect(binding:BINDING) {
* viewLifecycleOwner.lifecycleScope.launch {
* viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
* viewModel.events.collect { handleEvent(it) }
* }
* }
* }
* }
* ```
*
* @param BINDING The type of the DataBinding class.<br><br>
* DataBinding 클래스의 타입.<br>
* @param layoutRes The layout resource ID for the fragment.<br><br>
* Fragment의 레이아웃 리소스 ID.<br>
* @param isAttachToParent Whether to attach the inflated view to the parent container.<br><br>
* 인플레이션된 뷰를 부모 컨테이너에 첨부할지 여부.<br>
* Fragment에서는 특별한 경우가 아니라면 기본값 `false` 사용을 권장합니다.<br>
*
* @see ParentsBindingFragment For the parent class providing binding lifecycle.<br><br>
* 바인딩 생명주기를 제공하는 부모 클래스는 ParentsBindingFragment를 참조하세요.<br>
*
* @see BaseViewBindingFragment For ViewBinding-enabled Fragment.<br><br>
* ViewBinding을 사용하는 Fragment는 BaseViewBindingFragment를 참조하세요.<br>
*/
public abstract class BaseDataBindingFragment<BINDING : ViewDataBinding> : ParentsBindingFragment<BINDING> {
@LayoutRes
private val layoutRes: Int
constructor(layoutRes: Int) : super() {
this.layoutRes = layoutRes
}
constructor(layoutRes: Int, isAttachToParent: Boolean) : super(isAttachToParent) {
this.layoutRes = layoutRes
}
/**
* Creates the DataBinding instance using DataBindingUtil.<br>
* Note: lifecycleOwner is set later in onViewCreated() to viewLifecycleOwner for proper view lifecycle binding.<br><br>
* DataBindingUtil을 사용하여 DataBinding 인스턴스를 생성합니다.<br>
* 참고: lifecycleOwner는 적절한 뷰 생명주기 바인딩을 위해 onViewCreated()에서 viewLifecycleOwner로 설정됩니다.<br>
*
* @param inflater The LayoutInflater object to inflate views.<br><br>
* 뷰를 인플레이션할 LayoutInflater 객체.<br>
* @param container The parent view container.<br><br>
* 부모 뷰 컨테이너.<br>
* @param isAttachToParent Whether to attach to parent.<br><br>
* 부모에 첨부할지 여부.<br>
* Fragment에서는 특별한 경우가 아니라면 기본값 `false` 사용을 권장합니다.<br>
* @return The initialized DataBinding instance (lifecycleOwner will be set in onViewCreated).<br><br>
* 초기화된 DataBinding 인스턴스 (lifecycleOwner는 onViewCreated에서 설정됨).<br>
*/
final override fun createBinding(inflater: LayoutInflater, container: ViewGroup?, isAttachToParent: Boolean): BINDING =
DataBindingUtil.inflate<BINDING>(inflater, layoutRes, container, isAttachToParent)
/**
* Called immediately after onCreateView() has returned.<br>
* Sets the binding's lifecycleOwner to viewLifecycleOwner for proper LiveData observation tied to the view lifecycle.<br><br>
* onCreateView()가 반환된 직후 호출됩니다.<br>
* 뷰 생명주기에 연결된 적절한 LiveData 관찰을 위해 바인딩의 lifecycleOwner를 viewLifecycleOwner로 설정합니다.<br>
*
* **Important / 중요:**<br>
* - Using viewLifecycleOwner instead of Fragment's lifecycle ensures LiveData subscriptions are automatically cleaned up in onDestroyView().<br>
* - This prevents memory leaks and stale observations when Fragment's view is destroyed but Fragment instance persists (e.g., in back stack).<br><br>
* - Fragment의 생명주기 대신 viewLifecycleOwner를 사용하면 onDestroyView()에서 LiveData 구독이 자동으로 정리됩니다.<br>
* - 이는 Fragment의 뷰가 파괴되었지만 Fragment 인스턴스가 유지될 때(예: 백 스택) 메모리 누수와 오래된 관찰을 방지합니다.<br>
*
* @param view The View returned by onCreateView().<br><br>
* onCreateView()가 반환한 View.<br>
* @param savedInstanceState If non-null, this fragment is being re-constructed from a previous saved state.<br><br>
* null이 아닌 경우, 이 Fragment는 이전에 저장된 상태에서 다시 구성되고 있습니다.<br>
*/
final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
getBinding().lifecycleOwner = viewLifecycleOwner
super.onViewCreated(view, savedInstanceState)
}
/**
* Called when the view previously created by onCreateView has been detached from the fragment.<br>
* Cleans up the binding reference to prevent memory leaks by setting lifecycleOwner to null and calling unbind().<br><br>
* onCreateView에서 생성된 뷰가 Fragment에서 분리될 때 호출됩니다.<br>
* lifecycleOwner를 null로 설정하고 unbind()를 호출하여 메모리 누수를 방지하기 위해 바인딩 참조를 정리합니다.<br>
*/
@CallSuper
override fun onDestroyView() {
getBinding().lifecycleOwner = null
getBinding().unbind()
super.onDestroyView()
}
}