Очередная библиотека Android Reactive MVVM, за основу взята https://github.com/dmdevgo/RxPM
Основная цель библиотеки, это организовать направление данных View <-> ViewModel и сокрытия методов по изменению состояния напрямую из View.
Пример такого сокрытия вы могли видеть (или применять) при использовании LiveData
prvate val _state = MutableLiveData<DataType>()
val state: LiveData<DataType> = _state
Вместо этого, библиотека предлагает делать это так
Подключение:
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
dependencies {
implementation "com.github.AlexDeww:ReactiveViewModel:$last_version"
}
class SomeViewModel(
savedState: SavedStateHandle
) : ReactiveViewModel() {
val progressVisible by RVM.progressState(initialValue = false)
val tryCount by savedState.state(initialValue = 5)
val tryCountLabelVisible by RVM.stateProjection(tryCount) { it > 0 }
val sendButtonEnable by RVM.stateProjectionFromSource(initialValue = false) {
combineLatest(
progressVisible.observable,
tryCountLabelVisible.observable,
inputCode.data.observable.map { it.length >= 4 }
) { isProgress, hasTryCount, codeReached -> !isProgress && hasTryCount && codeReached }
}
val inputCode by savedState.inputControl()
val eventDone by RVM.eventNone()
val eventError by RVM.event<Throwable>()
val actionOnSendCodeClick by RVM.debouncedActionNone()
private val sendCode by RVM.invocable<String> { code ->
Completable
.fromAction {
tryCount.valueNonNull.takeIf { it > 0 }?.let { tryCount.setValue(it - 1) }
Log.d("VM", "Code sent: $code")
}
.delaySubscription(5, TimeUnit.SECONDS)
.bindProgress(progressVisible.consumer)
.doOnComplete { eventDone.call() }
.doOnError { eventError.call(it) }
}
init {
actionOnSendCodeClick.bind {
this.filter { sendButtonEnable.value == true }
.doOnNext { sendCode(inputCode.data.valueNonNull) }
}
}
}
class SomeFragment : ReactiveFragment() {
private val viewModel: SomeViewModel by Delegates.notNull()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.progressVisible.observe { progressView.isVisible = it }
viewModel.tryCount.observe { tryCountLabel.text = it.toString() }
viewModel.tryCountLabelVisible.observe { tryCountLabel.isVisible = it }
viewModel.sendButtonEnable.observe { sendButton.isEnabled = it }
viewModel.inputCode.bindTo(codeEditText)
viewModel.eventDone.observe { /* close fragment */ }
viewModel.eventError.observe { /* show error */ }
viewModel.actionOnSendCodeClick.bindOnClick(sendButton)
}
}
В основном предназначен для передачи состояния из ViewModel во View.
- Передавать данные в RvmState могут только наследники RvmPropertiesSupport.
- Всегда хранит последнее переданное значение.
- Каждый подписчик в первую очередь получит последннее сохраненное значение.
Объявление:
val state by RVM.state<DataType>(initialValue = null or data)
val state by savedStateHandle.state<DataType>(initialValue = null or data)
Подписка:
viewModel.state.observe { value -> /* do */ }
Почти тоже самое, что и RvmState, но отличается тем, что никто не может передавать данные напрямую.
- Никто не может передавать данные напрямую. RvmStateProjection может получать данные от источников: Observable, RvmState, RvmStateProjection, либо объекта наследника RvmPropertyBase и RvmValueProperty.
Объявление:
val state by RVM.state<DataType>(initialValue = null or data)
val stateProjection by RVM.stateProjection(state) { /* map block */ }
val stateProjection by RVM.stateProjectionFromSource(initialValue = null or data) { ObservableSource }
Подписка:
viewModel.stateProjection.observe { value -> /* do */ }
В основном предназначен для передачи событий или данных из ViewModel во View.
- Передавать данные в RvmEvent могут только наследники RvmPropertiesSupport.
- Хранит последнее переданное значение пока не появится подписчик. Только первый подписчик получит последнее сохраненное значение, все последующие подписки, будут получать только новые значения.
- Пока есть активная подписка, данные не сохраняются.
Объявление:
val event by RVM.event<DataType>()
val event by RVM.eventNone() // for Unit Data Type
Подписка:
viewModel.event.observe { value -> /* do */ }
Почти тоже самое, что и RvmEvent, но отличается тем, что хранит последнее значение пока не будет вызван метод confirm.
- Передавать данные в RvmConfirmationEvent могут только наследники RvmPropertiesSupport.
- Хранит последнее переданное значение пока не будет вызван метод confirm. Каждый новый подписчик будет получать последнее сохраненное значение, пока не вызван метод confirm.
Объявление:
val confirmationEvent by RVM.confirmationEvent<DataType>()
val confirmationEvent by RVM.confirmationEventNone() // for Unit Data Type
Подписка:
viewModel.confirmationEvent.observe { value ->
/* do */
viewModel.confirmationEvent.confirm()
}
В основном предназначен для передачи событий или данных из View во ViewModel.
- Не хранит данные.
Объявление:
val action by RVM.action<DataType>()
val action by RVM.actionNone() // for Unit Data Type
Привязка:
viewModel.action.bindOnClick(someView)
Для связи CompoundButton и ViewModel.
Объявление:
val checkControl by RVM.checkControl(initialChecked = false)
val checkControl by savedStateHandle.checkControl(initialChecked = false)
Привязка:
viewModel.checkControl.bindTo(compoundButton)
Для связи RatingBar и ViewModel.
Объявление:
val ratingControl by RVM.ratingControl(initialValue = 3)
val ratingControl by savedStateHandle.ratingControl(initialValue = 3)
Привязка:
viewModel.ratingControl.bindTo(ratingBar)
Для связи EditText или TextInputLayout и ViewModel.
Объявление:
val inputControl by RVM.inputControl(initialText = "Some Text")
val inputControl by savedStateHandle.inputControl(initialText = "Some Text")
Привязка:
viewModel.inputControl.bindTo(editText)
viewModel.inputControl.bindTo(textInputLayout)
Для связи Dialog и ViewModel.
Объявление:
val dialogControl by RVM.dialogControl<DataType, ResultDataType>()
val dialogControl by RVM.dialogControlDefaultResult<DataType>()
Привязка:
viewModel.dialogControl.bindTo { data: DataType, dc: RvmDialogControlResult<ResultDataType> ->
MaterialAlertDialogBuilder(context)
.title("Title")
.message("Message")
.setPositiveButton("OK") { _, _ -> dc.sendResult(ResultDataType) }
.setNegativeButton("Cancel") { _, _ -> dc.sendResult(ResultDataType) }
.create()
}