Skip to content
Merged

V3 #2

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d91f47d
refactoring for v2
Jan 4, 2023
5c84d2b
add new view binder
Jan 4, 2023
b325351
add widget bind shortcut
Jan 4, 2023
59547f0
reformat code
Jan 5, 2023
68b47ff
add RVM context for creation properties and widgets
Jan 5, 2023
7829fd6
rename "value" property to "data" on BaseVisualControl
Jan 5, 2023
3fc3fdb
add projection for state
Jan 5, 2023
bfbefde
autoDispose refactoring
Jan 5, 2023
d11c2b9
remove useless RvmWidgetsSupport.kt
Jan 5, 2023
e653bf3
up kotlin
Jan 6, 2023
65626c8
reduce rvm property extensions
Jan 6, 2023
de58ba3
hide consumer access for state projection
Jan 6, 2023
4c6713c
remove unused
Jan 6, 2023
746bde0
move state projection delegate to RVM scope
Jan 6, 2023
b8e8f1e
refactoring projection
Jan 6, 2023
d555f08
refactoring visualControl savedState delegate
Jan 6, 2023
1361050
make final for some extension
Jan 6, 2023
9af89d2
refactoring bind logic
Jan 7, 2023
f8d6f62
refactoring RvmPropertyDelegate
Jan 7, 2023
99fd265
add stateProjection from source
Jan 7, 2023
34cd3eb
rename stateProjection from source
Jan 7, 2023
e052a2f
fix stack overflow on extension implementation
Jan 7, 2023
9e0ed69
refactoring stateProjection
Jan 7, 2023
3fe0ff3
mark savedStateHandle.state @RvmDslMarker
Jan 7, 2023
2deddb7
update doc and readme
Jan 9, 2023
4577153
refactoring bind args names
Jan 9, 2023
5108a2b
refactoring stateProjectionFromSource make late subs on source
Jan 9, 2023
c590d0b
refactoring args naming
Jan 10, 2023
204546f
fix readme
Jan 10, 2023
c0a080e
refactoring widget naming
Jan 10, 2023
6554008
set lib version 3.0.1
Jan 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
333 changes: 229 additions & 104 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
# ReactiveViewModel

This is Android reactive MVVM library, fork https://github.com/dmdevgo/RxPM
Очередная библиотека Android Reactive MVVM, за основу взята https://github.com/dmdevgo/RxPM

Основная цель библиотеки, это организовать направление данных **View** <-> **ViewModel** и **сокрытия** методов по изменению состояния напрямую из **View**.

Пример такого сокрытия вы могли видеть (или применять) при использовании **LiveData**

```kotlin
prvate val _state = MutableLiveData<DataType>()
val state: LiveData<DataType> = _state
```

Вместо этого, библиотека предлагает делать это [так](#пример-viewmodel)

The Status of the lib:
[![](https://jitpack.io/v/AlexDeww/ReactiveViewModel.svg)](https://jitpack.io/#AlexDeww/ReactiveViewModel)

How to use this lib in your project:
Подключение:
```gradle
allprojects {
repositories {
Expand All @@ -15,7 +25,6 @@ allprojects {
}
```

Add to your app module build.gradle
```gradle
dependencies {
implementation "com.github.AlexDeww:ReactiveViewModel:$last_version"
Expand All @@ -24,147 +33,263 @@ dependencies {

### Пример ViewModel
```kotlin
class EnterSmsCodeViewModel(
val fullPhoneNumber: String,
private val requestSmsCode: RequestSmsCode,
private val registerOrSignIn: RegisterOrSignIn
): ReactiveViewModel() {
class SomeViewModel(
savedState: SavedStateHandle
) : ReactiveViewModel() {

private val eventCancelTimer = eventNone()
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 progressVisibility = state(initValue, PROGRESS_DEBOUNCE_INTERVAL)
val timerVisibility = state(false)
val timerValue = state<Long>()
val blocked = state(false)
val inputCode by savedState.inputControl()

val inputCode = inputControl()
val eventDone by RVM.eventNone()
val eventError by RVM.event<Throwable>()

val eventWrongSmsCode = eventNone()
val eventGoToTripStart = eventNone()
val eventGoToAcceptPolicy = eventNone()
val eventCodeExpired = eventNone()
val actionOnSendCodeClick by RVM.debouncedActionNone()

val actionSendCodeAgainClick = debouncedActionNone()

init {
inputCode.value.observable
.filter { blocked.value == false && progressVisibility.value == false }
.debounce()
.filter { it.length == SMS_CODE_LENGTH }
.doOnNext { analyticsManager.trackEvent(AppAnalyticEvents.ConfirmPhone) }
.switchMapSingle { register(it) }
.doOnError { inputCode.actionChangeValue.call("") }
.retry()
.subscribe {
when {
it -> eventGoToTripStart.call()
else -> eventGoToAcceptPolicy.call()
}
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")
}
.disposeOnCleared()

actionSendCodeAgainClick.observable
.filter { blocked.value == false && timerVisibility.value == false && progressVisibility.value == false }
.switchMapSingle { requestCode() }
.switchMap { getTimerObservable(it) }
.retry()
.subscribe()
.disposeOnCleared()

getRequestSmsTimerValue.execute(Unit)
.takeIf { it > 0 }
?.let { getTimerObservable(it) }
?.subscribe()
?.disposeOnCleared()
.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) }
}
}

}
```

### Связь с View
```kotlin
class EnterSmsCodeFragment : ReactiveFragment() {
class SomeFragment : ReactiveFragment() {

private val viewModel: SomeViewModel by Delegates.notNull()

companion object {
private const val ARG_FULL_PHONE_NUMBER = "EnterSmsCodeFragment.ARG_FULL_PHONE_NUMBER"

fun create(fullPhoneNumber: String): EnterSmsCodeFragment = EnterSmsCodeFragment()
.args { putString(ARG_FULL_PHONE_NUMBER, fullPhoneNumber) }
}

private val viewModel by viewModel<EnterSmsCodeViewModel> { // koin!!!
parametersOf(arguments?.getString(ARG_FULL_PHONE_NUMBER)!!)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

viewModel.progressVisibility.observe { if (it) showProgress() else hideProgress() }
viewModel.timerValue.observe { tvTimer.text = formatTimer(it) }
viewModel.timerVisibility.observe { tvTimer.isVisible = it }

viewModel.eventError.observe { showError(it) }
viewModel.eventGoToTripStart.observe { router.newRootScreen(Screens.Main.TripSetupFlowScreen()) }

viewModel.inputCode.bindTo(etSmsCode)
viewModel.actionSendCodeAgainClick.bindOnClick(btnSendAgain)

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)
}


}
```

## Есть 5 базовых объектов для взаимодействия View и ViewModel

### RvmState
В основном предназначен для передачи состояния из **ViewModel** во **View**.
- Передавать данные в **RvmState** могут только наследники **RvmPropertiesSupport**.
- Всегда хранит последнее переданное значение.
- Каждый подписчик в первую очередь получит последннее сохраненное значение.

Объявление:

```kotlin
val state by RVM.state<DataType>(initialValue = null or data)
```
```kotlin
val state by savedStateHandle.state<DataType>(initialValue = null or data)
```

Подписка:

```kotlin
viewModel.state.observe { value -> /* do */ }
```


### RvmStateProjection
Почти тоже самое, что и **RvmState**, но отличается тем, что никто не может передавать данные напрямую.
- Никто не может передавать данные напрямую. **RvmStateProjection** может получать данные от источников: **Observable**, **RvmState**, **RvmStateProjection**, либо объекта наследника **RvmPropertyBase** и **RvmValueProperty**.

Объявление:

```kotlin
val state by RVM.state<DataType>(initialValue = null or data)
val stateProjection by RVM.stateProjection(state) { /* map block */ }
```
```kotlin
val stateProjection by RVM.stateProjectionFromSource(initialValue = null or data) { ObservableSource }
```

Подписка:

```kotlin
viewModel.stateProjection.observe { value -> /* do */ }
```

### RvmEvent
В основном предназначен для передачи событий или данных из **ViewModel** во **View**.
- Передавать данные в **RvmEvent** могут только наследники **RvmPropertiesSupport**.
- Хранит последнее переданное значение пока не появится подписчик. Только первый подписчик получит последнее сохраненное значение, все последующие подписки, будут получать только новые значения.
- Пока есть активная подписка, данные не сохраняются.

Объявление:

```kotlin
val event by RVM.event<DataType>()
```
```kotlin
val event by RVM.eventNone() // for Unit Data Type
```

Подписка:

```kotlin
viewModel.event.observe { value -> /* do */ }
```

### RvmConfirmationEvent
Почти тоже самое, что и **RvmEvent**, но отличается тем, что хранит последнее значение пока не будет вызван метод **confirm**.
- Передавать данные в **RvmConfirmationEvent** могут только наследники **RvmPropertiesSupport**.
- Хранит последнее переданное значение пока не будет вызван метод **confirm**. Каждый новый подписчик будет получать последнее сохраненное значение, пока не вызван метод **confirm**.

Объявление:

```kotlin
val confirmationEvent by RVM.confirmationEvent<DataType>()
```
```kotlin
val confirmationEvent by RVM.confirmationEventNone() // for Unit Data Type
```

Подписка:

```kotlin
viewModel.confirmationEvent.observe { value ->
/* do */
viewModel.confirmationEvent.confirm()
}
```

### State
**State** хранит послдение значение и излучает его при подписке. Используется для передачи значения из ViewModel в View
### RvmAction
В основном предназначен для передачи событий или данных из **View** во **ViewModel**.
- Не хранит данные.

Объявление:

Создание
```kotlin
val isProgress = state<Boolean>(false)
val action by RVM.action<DataType>()
```
Из ViewModel
```kotlin
isProgress.consumer.accept(true)
isProgress.setValue(true) // расширение для isProgress.consumer.accept(true)
isProgress.setValueIfChanged(true) // расширение для isProgress.consumer.accept(true) но с проверкой if (lastValue != newValue)
val action by RVM.actionNone() // for Unit Data Type
```
В View

Привязка:

```kotlin
isProgress.observe { value -> }
viewModel.action.bindOnClick(someView)
```

### Action
**Action** ипользуется для передачи событий или параметров из View в ViewModel
## Также имеется 4 вспомогательных объекта для удобной связи View-элементов с ViewModel

### RvmCheckControl
Для связи **CompoundButton** и **ViewModel**.

Объявление:

Создание
```kotlin
val actionSendSmsCodeAgain = action<Unit>() // or actionEmpty() если тип Unit
val checkControl by RVM.checkControl(initialChecked = false)
```
Из ViewModel
```kotlin
actionSendSmsCodeAgain.consumer.accept(Unit)
actionSendSmsCodeAgain.call() // расширение для actionSendSmsCodeAgain.consumer.accept(Unit)
val checkControl by savedStateHandle.checkControl(initialChecked = false)
```
В View

Привязка:

```kotlin
actionSendSmsCodeAgain.bindOnClick(btnSendSmsCode)
btnSendSmsCode.setOnClickListener { actionSendSmsCodeAgain.call() }
viewModel.checkControl.bindTo(compoundButton)
```

### Event
**Event** ипользуется для передачи событий или параметров из ViewModel в View. Хранит последнее переданное значение, пока не появится подписчик.
### RvmRatingControl
Для связи **RatingBar** и **ViewModel**.

Объявление:

Создание
```kotlin
val eventDone = event<Unit>() // or eventEmpty() если тип Unit
val ratingControl by RVM.ratingControl(initialValue = 3)
```
Из ViewModel
```kotlin
eventDone.consumer.accept(Unit)
eventDone.call() // расширение для eventDone.consumer.accept(Unit)
val ratingControl by savedStateHandle.ratingControl(initialValue = 3)
```
В View

Привязка:

```kotlin
eventDone.observe { value -> }
viewModel.ratingControl.bindTo(ratingBar)
```

### RvmInputControl
Для связи **EditText** или **TextInputLayout** и **ViewModel**.

## Инфо
Вся либа, надстройка над LiveData. Cвойства(state, event) имею поле **liveData** для возможности совместного использования с **DataBinding**
Объявление:

```kotlin
val inputControl by RVM.inputControl(initialText = "Some Text")
```
```kotlin
val inputControl by savedStateHandle.inputControl(initialText = "Some Text")
```

Привязка:

```kotlin
viewModel.inputControl.bindTo(editText)
```
```kotlin
viewModel.inputControl.bindTo(textInputLayout)
```

### RvmDialogControl
Для связи **Dialog** и **ViewModel**.

Объявление:

```kotlin
val dialogControl by RVM.dialogControl<DataType, ResultDataType>()
```
```kotlin
val dialogControl by RVM.dialogControlDefaultResult<DataType>()
```

Привязка:

```kotlin
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()
}
```
Loading