본문으로 바로가기

뷰와 관련된 것을 처리하는 코드와 데이터를 처리하는 코드를 서로 분리하는 것은 중요합니다.

우리는 관심사 분리 법칙에 따라 유지 보수가 편하도록 코드를 병렬형으로 짜고 클래스나 오브젝트 당 본인이 담당하는 일을 최대한 분리해야 하기 때문입니다.

 

그렇게 하지 않으면 짜는 코드마다 버그 투성이에, 유지보수에는 골머리를 앓게 될 것입니다. 

 

이 관심사 분리를 실현하기 위해서 Data Binding 을 사용하면 되겠습니다.

 

실 사용예시는 다음과 같습니다.

 

BindingAdapter.kt

@BindingAdapter("isGone")
fun bindIsGone(view: View, isGone: Boolean) {
    view.visibility = if (isGone) {
        View.VISIBLE
    } else {
        View.GONE
    }
}

fragment_setting.xml

<?xml version="1.0" encoding="utf-8"?>

<layout 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"
    tools:context=".Fragments.SettingFragment">

    <data>
        <variable
            name="IsSigned"
            type="boolean"/>
    </data>
    
    <androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/userlayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:isGone="@{IsSigned}">
    
.....

BindingAdapter 스크립트를 만들어 로그인을 했을 때만 특정 뷰를 보여주도록 IsGone 이라는 Boolean 속성을 선언해주고, 

xml 에서 로그인을 했을 때만 보여주고 싶은 뷰에다 isGone 속성을 추가해 IsSigned 변수를 지정해 줍니다.

 

SettingFragment.kt

@AndroidEntryPoint
class SettingFragment : Fragment(), LanguageDialogFragment.OnLanguageSelected {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View? {
        binding = FragmentSettingBinding.inflate(inflater,container,false)
        
        binding.apply {
        	isSgined = auth.currentUser.isAnnonymous 
        }
        return binding.root
    }
    
    ///...
    
}

그리고 Fragment 클래스에서 IsSigned 변수를 상태에 따라 초기화 할 수 있도록 합니다.

 

다음과 같이 Glide를 좀더 쓰기 편하도록 사용할 수도 있습니다.

@BindingAdapter("imageFromUrl")
fun bindImageFromUrl(view: ImageView, imageUrl: String?) {
    if (!imageUrl.isNullOrEmpty()) {
        Glide.with(view.context)
            .load(imageUrl)
            .transition(DrawableTransitionOptions.withCrossFade())
            .into(view)
    }
}

pokemon_info.xml

<?xml version="1.0" encoding="utf-8"?>

<layout  xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".Fragments.PokemonInfoFragment">
    
    <data>
        <variable
            name="viewModel"
            type="com.unitewikiapp.unitewiki.viewmodels.PokemonInfoViewModel" />
    </data>

<ImageView
    android:id="@+id/pokemon_pic"
    android:layout_width="70dp"
    android:layout_height="70dp"
    app:imageFromUrl="@{viewModel.infodata.skill1}"/>
    
    
    ....

PokemonInfoFragment.kt

@AndroidEntryPoint
class PokemonInfoFragment : Fragment() {

    private val viewmodel:PokemonInfoViewModel by viewModels()
    
     override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View? {
        binding = FragmentPokemonInfoBinding.inflate(inflater,container,false)
    	
        binding.apply{
        	viewModel = viewmodel
        	lifecycleOwner = viewLifecycleOwner
        }
	}
    
    
    
    ///....
    
}

프래그먼트 클래스에서 Glide에 관한 복잡한 코드를 쓸 필요가 없이 BindingAdapter를 통해 viewModel 변수를 가져와 Glide 를 통해 이미지를 표시한 것을 알 수가 있습니다. 

 

Glide 는 그림을 url로 표시할 수 있도록 도와주는 라이브러리로 반복된 코드를 줄이기 위해 BindingAdapter를 통해 처리하는 것이 낫습니다.

 

위 예시 코드에서처럼 viewmodel을 변수로 사용할 경우 data binding 라이브러리의 lifecyclerOwner 변수를 viewmode 라이브러리의 viewLifecycleOwner 라고 선언해줘야 합니다.

이걸 해주지 않으면 repository에서 비동기 방식으로 데이터를 얻어오고 있으므로 observing 을 해주는 방식으로 viewmodel 변수를 선언해줘야 하는데 , lifecyclerOwner(데이터 바인딩 라이브러리) = viewLifecycleOwener(뷰 모델 라이브러리) 라고 선언해 준다면 자동으로 observing 해주어 viewmodel의 변수들을 적절한 형태로 사용할 수 있게 됩니다.

 

위 예시들처럼 DataBiding 라이브러리는 어떤 '상태'에 따라 해당 뷰만 보여주도록 하는 Boolean 상태를 구현할 수도 있고, Glide 에서의 예시처럼 프래그먼트 내 클래스에서 복잡한 코드를 작성할 필요 없이 뷰와 관련된 것은 뷰에서만 처리하도록 관심사 분리 법칙을 실현할 수도 있습니다.

 

유나이트 위키는 DataBinding 라이브러리를 사용하여 위 예시와 같이 작성되었습니다.