뷰와 관련된 것을 처리하는 코드와 데이터를 처리하는 코드를 서로 분리하는 것은 중요합니다.
우리는 관심사 분리 법칙에 따라 유지 보수가 편하도록 코드를 병렬형으로 짜고 클래스나 오브젝트 당 본인이 담당하는 일을 최대한 분리해야 하기 때문입니다.
그렇게 하지 않으면 짜는 코드마다 버그 투성이에, 유지보수에는 골머리를 앓게 될 것입니다.
이 관심사 분리를 실현하기 위해서 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 라이브러리를 사용하여 위 예시와 같이 작성되었습니다.