Skip to content

Instantly share code, notes, and snippets.

@nectarine
Last active July 3, 2017 10:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nectarine/a195daf0ebe0ad0786a8704fbe3e4507 to your computer and use it in GitHub Desktop.
Save nectarine/a195daf0ebe0ad0786a8704fbe3e4507 to your computer and use it in GitHub Desktop.
Android MVVM with Custom State Widget
<declare-styleable name="ElementStatus">
<attr name="state_on" format="boolean" />
<attr name="state_loading" format="boolean" />
</declare-styleable>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:drawable="@drawable/round_rectangle_status_on" app:state_loading="true" />
<item android:drawable="@drawable/round_rectangle_status_on" app:state_on="true" />
<item android:drawable="@drawable/round_rectangle_status_off" />
</selector>
abstract class ElementViewModel<T : Element> : RxViewModel() {
lateinit var element: T
protected fun initialize(element: T) {
this.element = element
isOn.set(element.isOn())
onStatusUpdate()
}
val isEnabled: ObservableField<Boolean> = ObservableField()
val isLoading: ObservableField<Boolean> = ObservableField(false)
val isOn: ObservableField<Boolean> = ObservableField()
open fun onStatusToggle(button: View) {
val currentStatus = element.status
if (element.isOn()) {
element.off()
} else {
element.on()
}
addDisposables(
apiRepository.modifyElementStatus(element, element.status!!)
.compose(UiModel.apiRequestTransformer)
.subscribe({
if (it is UiModel.Loading) {
isLoading.set(true)
button.isEnabled = false
} else {
if (it is UiModel.Failure) {
ToastUtils.show(R.string.formatter_element_status_control_failed, element.getDisplayName())
element.status = currentStatus
}
isLoading.set(false)
isOn.set(element.isOn())
button.isEnabled = true
onStatusUpdate()
}
})
)
}
open fun onStatusUpdate() {
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="vm"
type="com.acciio.homeapp.ui.main.home.viewmodel.LightViewModel" />
</data>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="112dp">
<TextView
android:id="@+id/textView8"
style="@style/Text.Caption2.CardDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:text="@{vm.statusText}"
app:layout_constraintEnd_toStartOf="@+id/guideline6"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toTopOf="parent" />
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="16dp" />
<android.support.constraint.Guideline
android:id="@+id/guideline6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="16dp" />
<com.acciio.homeapp.custom.view.state.StateImageButton
android:id="@+id/imageButton"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:background="@drawable/background_element_status"
android:onClick="@{(v) -> vm.onStatusToggle(v)}"
android:scaleType="center"
android:src="@drawable/icon_light_status"
app:layout_constraintEnd_toStartOf="@+id/guideline6"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/textView8"
app:state_loading="@{safeUnbox(vm.isLoading)}"
app:state_on="@{safeUnbox(vm.isOn)}" />
</android.support.constraint.ConstraintLayout>
</layout>
class StateImageButton @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageButton(context, attrs, defStyleAttr) {
var isOn = false
set(value) {
field = value
refreshDrawableState()
}
var isLoading = false
set(value) {
field = value
refreshDrawableState()
}
override fun onCreateDrawableState(extraSpace: Int): IntArray {
val drawableState = super.onCreateDrawableState(extraSpace + EXTRA_STATES.size)
if (isLoading) {
AppCompatButton.mergeDrawableStates(drawableState, intArrayOf(EXTRA_STATES[0]))
}
if (isOn) {
AppCompatButton.mergeDrawableStates(drawableState, intArrayOf(EXTRA_STATES[1]))
}
return drawableState
}
override fun drawableStateChanged() {
super.drawableStateChanged()
}
companion object {
private val EXTRA_STATES = intArrayOf(R.attr.state_loading, R.attr.state_on)
}
}
object ViewBindingAdapter {
@JvmStatic
@BindingAdapter("app:state_loading")
fun setLoading(view: StateImageButton, isLoading: Boolean) {
view.isLoading = isLoading
}
@JvmStatic
@BindingAdapter("app:state_on")
fun setStatus(view: StateImageButton, isOn: Boolean) {
view.isOn = isOn
}
@JvmStatic
@BindingAdapter("app:state_loading")
fun setLoading(view: StateButton, isLoading: Boolean) {
view.isLoading = isLoading
}
@JvmStatic
@BindingAdapter("app:state_on")
fun setStatus(view: StateButton, isOn: Boolean) {
view.isOn = isOn
}
@JvmStatic
@BindingAdapter("app:error")
fun setError(view: TextInputLayout, error: String) {
view.error = error
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment