Поле BaseObservable внутри архитектуры ViewModel не добавляет никакого обратного вызова наблюдателя - PullRequest
0 голосов
/ 23 мая 2019

Я исследую новый компонент архитектуры ViewModel, намереваясь перенести в него мое приложение, активность за деятельностью. Мое приложение уже использует привязку данных и имеет некоторые объекты модели в качестве BaseObservable. В настоящее время у меня есть одно действие, которое выполняет вызов API, и в случае сбоя вызова я возвращаю изменение в пользовательском интерфейсе. Я использую двустороннюю привязку для этого. Итак, теперь я создаю класс ViewModel для действия и перемещаю к нему некоторую бизнес-логику и вызов API. После этого последовательность возврата при неудачном вызове API больше не обновляет пользовательский интерфейс. Я вижу, что изменение значения достигает моей модели BaseObservable, которая вызывает метод notifyChanged, однако модель имеет mCallbacks == null, поэтому нет никаких слушателей для обновления пользовательского интерфейса после изменений модели. Вот кусочки:

Активная часть OnCreate

this.dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_settings)
val viewModel = AppSettingsViewModel(this.application)
this.dataBinding.viewModel = viewModel
this.dataBinding.setLifecycleOwner(this)
this.lifecycle.addObserver(this.dataBinding.viewModel!!)
this.dataBinding.context = this

Часть макета:

<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">
   <data>
      <variable name="context" type="letstwinkle.com.twinkle.AppSettingsActivity" />
      <variable name="viewModel" type="letstwinkle.com.twinkle.viewmodel.AppSettingsViewModel"/>
      ...imports...
   </data>

   <androidx.drawerlayout.widget.DrawerLayout
      android:id="@+id/drawer"
      android:layout_width="match_parent" android:layout_height="match_parent"
      >

   ...more nested views...
                  <Switch
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:layout_marginTop="15dp"
                     android:layout_marginStart="12dp"
                     android:text="@string/new_matches"
                     android:checked="@={viewModel.settings.pnMatches}"
                     android:textColor="@color/settingsItem"
                     android:onClick="@{(v) -> viewModel.toggleNewMatches(((Checkable)v).isChecked)}"
                     android:enabled="@{viewModel.settings != null}"
                     />
</layout>

И, наконец, ViewModel / SettingsModel

internal class AppSettingsViewModel(app: Application) : AndroidViewModel(app), VolleyErrorObservable,
    ResponseHandler<SubmitResult>, LifecycleObserver
{
    var settings: SettingsModel? = null
    val account = User.account
    val demoMessageText = MutableLiveData<String>()
    override val volleyError = MutableLiveData<VolleyError>()

    fun toggleNewMatches(newValue: Boolean) {
        this.twinkleApplication.trackEvent("click:togglesetting", "App Settings Toggle Setting",
                                           Bundle().apply { putString("setting", "pn_match") })
        updateBoolSetting("pn_match", newValue) { set, b -> set.pnMatches = b}
    }

    private fun updateBoolSetting(setting: String, newValue: Boolean, reverter: (SettingsModel, Boolean) -> Unit) {
        val obj = this.settings!!.jsonForSetting(setting, newValue)
        val suh = SettingsUpdateHandler(this.settings!!, setting == "mp_enabled")
            { settings -> reverter(settings, !newValue) }
        APIClient.updateSettings(obj, suh)
    }
    ...
}

private class SettingsUpdateHandler(val settings: SettingsModel,
                                    val isMPEnabled: Boolean = false,
                                    val revertFun: (SettingsModel) -> Unit)
    : ResponseHandler<SubmitResult>
{
    override fun onErrorResponse(error: VolleyError) {
        revertFun(settings)
        if (weakToast?.get()?.view?.isShown == true) {
            weakToast?.get()?.cancel()
        }
        val toast = Toast.makeText(TwinkleApplication.instance,
                                   R.string.failed_save_settings, Toast.LENGTH_LONG)
        ...
    }
    override fun onResponse(response: SubmitResult) {
        ...
    }

}

@Table(database = Database::class, useBooleanGetterSetters = false)
internal class SettingsModel() : BaseObservable(), Model {
    @NotNull @Column @get:Bindable var pnMatches: Boolean = false
        set(value) {
            Log.d("SettingsModel", "set pnMatches: value=$value, field=$field")
            val changed = field != value
            field = value
            notifyIfChanged(BR.pnMatches, changed)
        }
    ...
    private inline fun notifyIfChanged(field: Int, changed: Boolean) {
        if (changed)
            notifyPropertyChanged(field)
    }
}

Я ожидаю, что коммутатор будет переключен обратно, когда значение viewModel.settings.pnMatches вернется обратно, но это не так.

1 Ответ

0 голосов
/ 29 мая 2019

Я нашел ответ на одной из страниц документации по привязке данных, которая включает обсуждение использования с ViewModel / LiveData: ViewModel необходимо реализовать сам Observable и использовать аннотацию Bindable в поле Observable, которое не является LiveData:

internal class AppSettingsViewModel(app: Application) : AndroidViewModel(app), VolleyErrorObservable,
    ResponseHandler<SubmitResult>, NotifiableObservable by BaseNotifiableObservable()
{
    @get:Bindable
    var settings: SettingsModel? = null
.....

Бит NotifiableObservable - это удобная идиома Котлина для реализации Observable делегированием, которую я нашел в Интернете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...