Skip to content

Instantly share code, notes, and snippets.

@n8ebel
Last active July 8, 2021 18:40
Show Gist options
  • Save n8ebel/0a38941d12dfd66fe9421f32ef8e73ff to your computer and use it in GitHub Desktop.
Save n8ebel/0a38941d12dfd66fe9421f32ef8e73ff to your computer and use it in GitHub Desktop.
BindingAdapters for separation of viewmodels and Android framework for resource loading

Creating binding adapters that take resource ids allow view models to ignore the loading of resources like strings, colors, and drawables

  • View models are then free to deal in resource ids which are independent of the Android framework and are easy to test
  • Define globally applicable binding adapters at the root level in BindingAdapters.kt
  • Define feature/package specific adapters within the package as _BindingAdapters.kt
    • the prefix allows that file to appear at the top of the package contents list which makes it easier to find, and remember
<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">
<data>
<variable
name="viewModel"
type="com.n8ebel.databindingsandbox.main.MainViewModel" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:backgroundColor="@{viewModel.itemBackgroundId}"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="16dp"
android:gravity="center"
app:backgroundDrawable="@{viewModel.screenBackgroundId}"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@{viewModel.messageResId}"
app:textColor="@{viewModel.messageColorId}"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:mainSubtitleText="@{viewModel.number}"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me"
android:onClick="@{() -> viewModel.handleClick()}"
/>
</LinearLayout>
</FrameLayout>
</layout>
// I would typically place this adapter in main/_BindingAdapters.kt
// The gist tool won't allow that filename structure
//
@BindingAdapter("mainSubtitleText")
fun setText(view: TextView, value:Int) {
view.text = view.context.getString(R.string.main_subtitle_format, value)
}
@BindingAdapter("textColor")
fun setTextColor(view: TextView, @ColorRes resId: Int) = view.setTextColor(ContextCompat.getColor(view.context, resId))
@BindingAdapter("backgroundColor")
fun setBackground(view: View, @ColorRes resId: Int) {
view.setBackgroundColor(ContextCompat.getColor(view.context, resId))
}
@BindingAdapter("backgroundDrawable")
fun setBackgroundDrawable(view: View, @DrawableRes resId: Int) {
view.background = ContextCompat.getDrawable(view.context, resId)
}
class MainActivity : AppCompatActivity(), ViewModelListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main).bindData {
viewModel = MainViewModel(R.string.hello, 5, R.color.colorAccent, this@MainActivity)
}
}
// region implements ViewModelListener
override fun showMessage(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
// endregion implements ViewModelListener
}
class MainViewModel(@StringRes msgId:Int, num:Int, @ColorRes msgColorId:Int, val listener: ViewModelListener) {
val messageResId = ObservableInt(msgId)
val number = ObservableInt(num)
val messageColorId = ObservableInt(msgColorId)
val screenBackgroundId = ObservableInt(R.drawable.background_gradient)
val itemBackgroundId = ObservableInt(R.color.colorPrimaryDark)
@BoundFunction
fun handleClick() {
listener.showMessage("You Clicked It!!")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment