Coverage Summary for Class: BaseDataBindingDialogFragment (kr.open.library.simple_ui.xml.ui.components.dialog.binding)

Class Class, % Method, % Branch, % Line, % Instruction, %
BaseDataBindingDialogFragment 100% (1/1) 80% (4/5) 80% (8/10) 85.4% (35/41)


 package kr.open.library.simple_ui.xml.ui.components.dialog.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
 import kr.open.library.simple_ui.xml.ui.components.dialog.root.RootDialogFragment
 
 /**
  * A base DialogFragment class that uses DataBinding and provides common functionality for data-bound dialogs.<br>
  * Extends ParentBindingViewDialogFragment to inherit binding lifecycle and ViewModel event collection.<br><br>
  * DataBinding을 사용하고 데이터 바인딩 다이얼로그에 대한 공통 기능을 제공하는 기본 DialogFragment 클래스입니다.<br>
  * ParentBindingViewDialogFragment를 확장하여 바인딩 생명주기와 ViewModel 이벤트 수집을 상속받습니다.<br>
  *
  * **Why this class exists / 이 클래스가 필요한 이유:**<br>
  * - Android's DataBinding requires manual DataBindingUtil.inflate() calls for each DialogFragment.<br>
  * - DataBinding enables automatic UI updates when LiveData values change, reducing boilerplate update code (StateFlow requires manual collection).<br>
  * - This class automatically sets lifecycleOwner to enable LiveData observation, which developers often forget.<br>
  * - Provides a centralized place for DataBinding-specific cleanup (unbind, null lifecycleOwner).<br><br>
  * - Android의 DataBinding은 각 DialogFragment마다 수동으로 DataBindingUtil.inflate() 호출이 필요합니다.<br>
  * - DataBinding은 LiveData 값이 변경될 때 자동 UI 업데이트를 가능하게 하여 보일러플레이트 업데이트 코드를 줄입니다 (StateFlow는 수동 수집 필요).<br>
  * - 이 클래스는 개발자가 자주 잊는 LiveData 관찰을 활성화하기 위해 자동으로 lifecycleOwner를 설정합니다.<br>
  * - DataBinding 전용 정리(unbind, null lifecycleOwner)를 위한 중앙화된 장소를 제공합니다.<br>
  *
  * **Design decisions / 설계 결정 이유:**<br>
  * - Uses constructor parameter for layout resource ID to match Android's standard DataBinding pattern.<br>
  * - Automatically sets binding.lifecycleOwner in onViewCreated() to viewLifecycleOwner for proper view lifecycle binding.<br>
  * - Implements final createBinding() to prevent subclasses from breaking the DataBinding initialization contract.<br>
  * - Calls unbind() and nulls lifecycleOwner in onDestroyView() to prevent memory leaks from LiveData observers.<br><br>
  * - Android의 표준 DataBinding 패턴과 일치하도록 생성자 파라미터로 레이아웃 리소스 ID를 사용합니다.<br>
  * - 적절한 뷰 생명주기 바인딩을 위해 onViewCreated()에서 자동으로 binding.lifecycleOwner를 viewLifecycleOwner로 설정합니다.<br>
  * - final createBinding()을 구현하여 하위 클래스가 DataBinding 초기화 계약을 깨는 것을 방지합니다.<br>
  * - LiveData 옵저버로 인한 메모리 누수를 방지하기 위해 onDestroyView()에서 unbind()를 호출하고 lifecycleOwner를 null로 설정합니다.<br>
  *
  * **Important notes / 주의사항:**<br>
  * - ⚠️ CRITICAL: lifecycleOwner is automatically set to viewLifecycleOwner in onViewCreated(). Unlike Activity's BaseDataBindingActivity, you don't need to set it manually.<br>
  * - DataBinding automatically updates UI when LiveData values change - no need for manual observe() calls in XML-bound properties (StateFlow requires manual collection).<br>
  * - Always use repeatOnLifecycle(Lifecycle.State.STARTED) inside onEventVmCollect(binding:BINDING) to properly handle configuration changes.<br>
  * - Access the binding object via getBinding() method after super.onViewCreated() completes.<br><br>
  * - ⚠️ 중요: lifecycleOwner는 onViewCreated()에서 viewLifecycleOwner로 자동 설정됩니다. Activity의 BaseDataBindingActivity와 달리 수동으로 설정할 필요가 없습니다.<br>
  * - DataBinding은 LiveData 값이 변경될 때 자동으로 UI를 업데이트합니다 - XML에 바인딩된 프로퍼티에 대해 수동 observe() 호출이 필요하지 않습니다 (StateFlow는 수동 수집 필요).<br>
  * - 구성 변경을 올바르게 처리하려면 onEventVmCollect(binding:BINDING) 내부에서 항상 repeatOnLifecycle(Lifecycle.State.STARTED)를 사용하세요.<br>
  * - super.onViewCreated() 완료 후 getBinding() 메서드를 통해 바인딩 객체에 접근하세요.<br>
  *
  * **Usage / 사용법:**<br>
  * 1. Extend this class with your DialogFragment and pass the layout resource ID.<br>
  * 2. Access views through getBinding() in onViewCreated() or override onViewCreated(binding, savedInstanceState) for initialization.<br>
  * 3. Override onEventVmCollect(binding:BINDING) to collect ViewModel events with repeatOnLifecycle.<br>
  * 4. LiveData properties bound in XML will automatically update - no manual observation needed.<br><br>
  * 1. DialogFragment에서 이 클래스를 상속받고 레이아웃 리소스 ID를 전달하세요.<br>
  * 2. onViewCreated()에서 getBinding()을 통해 뷰에 접근하거나 초기화를 위해 onViewCreated(binding, savedInstanceState)를 오버라이드하세요.<br>
  * 3. repeatOnLifecycle과 함께 ViewModel 이벤트를 수집하려면 onEventVmCollect(binding:BINDING)를 오버라이드하세요.<br>
  * 4. XML에 바인딩된 LiveData 프로퍼티는 자동으로 업데이트됩니다 - 수동 관찰이 필요하지 않습니다.<br>
  *
  * **Usage example:**<br>
  * ```kotlin
  * class ConfirmDialog : BaseDataBindingDialogFragment<DialogConfirmBinding>(R.layout.dialog_confirm) {
  *     private val viewModel: ConfirmViewModel by lazy { getViewModel() }
  *
  *     override fun onViewCreated(binding: DialogConfirmBinding, savedInstanceState: Bundle?) {
  *         binding.viewModel = viewModel
  *         // lifecycleOwner is already set automatically - no need to set it manually
  *     }
  *
  *     override fun onEventVmCollect(binding:BINDING) {
  *         viewLifecycleOwner.lifecycleScope.launch {
  *             repeatOnLifecycle(Lifecycle.State.STARTED) {
  *                 viewModel.dismissEvent.collect { safeDismiss() }
  *             }
  *         }
  *     }
  * }
  * ```
  * <br>
  *
  * @param BINDING The type of the DataBinding class.<br><br>
  *                DataBinding 클래스의 타입.<br>
  *
  * @param layoutRes The layout resource ID for the dialog.<br><br>
  *                  다이얼로그의 레이아웃 리소스 ID.<br>
  *
  * @param isAttachToParent Whether to attach the inflated view to the parent container.<br><br>
  *                         인플레이션된 뷰를 부모 컨테이너에 첨부할지 여부.<br>
  *                         DialogFragment에서는 특별한 경우가 아니라면 기본값 `false` 사용을 권장합니다.<br>
  *
  * @see ParentBindingViewDialogFragment For the abstract parent class of all binding-enabled dialog fragments.<br><br>
  *      모든 바인딩 지원 DialogFragment의 추상 부모 클래스는 ParentBindingViewDialogFragment를 참조하세요.<br>
  *
  * @see RootDialogFragment For base class with dialog and permission features.<br><br>
  *      다이얼로그 및 권한 기능이 있는 기본 클래스는 RootDialogFragment를 참조하세요.<br>
  *
  * @see BaseDialogFragment For simple layout-based DialogFragment without DataBinding.<br><br>
  *      DataBinding 없이 간단한 레이아웃 기반 DialogFragment는 BaseDialogFragment를 참조하세요.<br>
  *
  * @see BaseViewBindingDialogFragment For ViewBinding-enabled DialogFragment.<br><br>
  *      ViewBinding을 사용하는 DialogFragment는 BaseViewBindingDialogFragment를 참조하세요.<br>
  */
 public abstract class BaseDataBindingDialogFragment<BINDING : ViewDataBinding> : ParentBindingViewDialogFragment<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>
      *                         DialogFragment에서는 특별한 경우가 아니라면 기본값 `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>
      */
     @CallSuper
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         // viewLifecycleOwner가 준비된 시점에 설정
         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()
     }
 }