Skip to content

Commit 43b3da8

Browse files
authored
Merge pull request #2 from AlexDeww/v3
V3
2 parents c8ac0e9 + 6554008 commit 43b3da8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1844
-1229
lines changed

README.md

Lines changed: 229 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
# ReactiveViewModel
22

3-
This is Android reactive MVVM library, fork https://github.com/dmdevgo/RxPM
3+
Очередная библиотека Android Reactive MVVM, за основу взята https://github.com/dmdevgo/RxPM
4+
5+
Основная цель библиотеки, это организовать направление данных **View** <-> **ViewModel** и **сокрытия** методов по изменению состояния напрямую из **View**.
6+
7+
Пример такого сокрытия вы могли видеть (или применять) при использовании **LiveData**
8+
9+
```kotlin
10+
prvate val _state = MutableLiveData<DataType>()
11+
val state: LiveData<DataType> = _state
12+
```
13+
14+
Вместо этого, библиотека предлагает делать это [так](#пример-viewmodel)
415

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

8-
How to use this lib in your project:
18+
Подключение:
919
```gradle
1020
allprojects {
1121
repositories {
@@ -15,7 +25,6 @@ allprojects {
1525
}
1626
```
1727

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

2534
### Пример ViewModel
2635
```kotlin
27-
class EnterSmsCodeViewModel(
28-
val fullPhoneNumber: String,
29-
private val requestSmsCode: RequestSmsCode,
30-
private val registerOrSignIn: RegisterOrSignIn
31-
): ReactiveViewModel() {
36+
class SomeViewModel(
37+
savedState: SavedStateHandle
38+
) : ReactiveViewModel() {
3239

33-
private val eventCancelTimer = eventNone()
40+
val progressVisible by RVM.progressState(initialValue = false)
41+
val tryCount by savedState.state(initialValue = 5)
42+
43+
val tryCountLabelVisible by RVM.stateProjection(tryCount) { it > 0 }
44+
val sendButtonEnable by RVM.stateProjectionFromSource(initialValue = false) {
45+
combineLatest(
46+
progressVisible.observable,
47+
tryCountLabelVisible.observable,
48+
inputCode.data.observable.map { it.length >= 4 }
49+
) { isProgress, hasTryCount, codeReached -> !isProgress && hasTryCount && codeReached }
50+
}
3451

35-
val progressVisibility = state(initValue, PROGRESS_DEBOUNCE_INTERVAL)
36-
val timerVisibility = state(false)
37-
val timerValue = state<Long>()
38-
val blocked = state(false)
52+
val inputCode by savedState.inputControl()
3953

40-
val inputCode = inputControl()
54+
val eventDone by RVM.eventNone()
55+
val eventError by RVM.event<Throwable>()
4156

42-
val eventWrongSmsCode = eventNone()
43-
val eventGoToTripStart = eventNone()
44-
val eventGoToAcceptPolicy = eventNone()
45-
val eventCodeExpired = eventNone()
57+
val actionOnSendCodeClick by RVM.debouncedActionNone()
4658

47-
val actionSendCodeAgainClick = debouncedActionNone()
48-
49-
init {
50-
inputCode.value.observable
51-
.filter { blocked.value == false && progressVisibility.value == false }
52-
.debounce()
53-
.filter { it.length == SMS_CODE_LENGTH }
54-
.doOnNext { analyticsManager.trackEvent(AppAnalyticEvents.ConfirmPhone) }
55-
.switchMapSingle { register(it) }
56-
.doOnError { inputCode.actionChangeValue.call("") }
57-
.retry()
58-
.subscribe {
59-
when {
60-
it -> eventGoToTripStart.call()
61-
else -> eventGoToAcceptPolicy.call()
62-
}
59+
private val sendCode by RVM.invocable<String> { code ->
60+
Completable
61+
.fromAction {
62+
tryCount.valueNonNull.takeIf { it > 0 }?.let { tryCount.setValue(it - 1) }
63+
Log.d("VM", "Code sent: $code")
6364
}
64-
.disposeOnCleared()
65-
66-
actionSendCodeAgainClick.observable
67-
.filter { blocked.value == false && timerVisibility.value == false && progressVisibility.value == false }
68-
.switchMapSingle { requestCode() }
69-
.switchMap { getTimerObservable(it) }
70-
.retry()
71-
.subscribe()
72-
.disposeOnCleared()
73-
74-
getRequestSmsTimerValue.execute(Unit)
75-
.takeIf { it > 0 }
76-
?.let { getTimerObservable(it) }
77-
?.subscribe()
78-
?.disposeOnCleared()
65+
.delaySubscription(5, TimeUnit.SECONDS)
66+
.bindProgress(progressVisible.consumer)
67+
.doOnComplete { eventDone.call() }
68+
.doOnError { eventError.call(it) }
69+
}
70+
71+
init {
72+
actionOnSendCodeClick.bind {
73+
this.filter { sendButtonEnable.value == true }
74+
.doOnNext { sendCode(inputCode.data.valueNonNull) }
75+
}
7976
}
77+
8078
}
8179
```
8280

8381
### Связь с View
8482
```kotlin
85-
class EnterSmsCodeFragment : ReactiveFragment() {
83+
class SomeFragment : ReactiveFragment() {
84+
85+
private val viewModel: SomeViewModel by Delegates.notNull()
8686

87-
companion object {
88-
private const val ARG_FULL_PHONE_NUMBER = "EnterSmsCodeFragment.ARG_FULL_PHONE_NUMBER"
89-
90-
fun create(fullPhoneNumber: String): EnterSmsCodeFragment = EnterSmsCodeFragment()
91-
.args { putString(ARG_FULL_PHONE_NUMBER, fullPhoneNumber) }
92-
}
93-
94-
private val viewModel by viewModel<EnterSmsCodeViewModel> { // koin!!!
95-
parametersOf(arguments?.getString(ARG_FULL_PHONE_NUMBER)!!)
96-
}
97-
9887
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
9988
super.onViewCreated(view, savedInstanceState)
100-
101-
viewModel.progressVisibility.observe { if (it) showProgress() else hideProgress() }
102-
viewModel.timerValue.observe { tvTimer.text = formatTimer(it) }
103-
viewModel.timerVisibility.observe { tvTimer.isVisible = it }
104-
105-
viewModel.eventError.observe { showError(it) }
106-
viewModel.eventGoToTripStart.observe { router.newRootScreen(Screens.Main.TripSetupFlowScreen()) }
107-
108-
viewModel.inputCode.bindTo(etSmsCode)
109-
viewModel.actionSendCodeAgainClick.bindOnClick(btnSendAgain)
89+
90+
viewModel.progressVisible.observe { progressView.isVisible = it }
91+
viewModel.tryCount.observe { tryCountLabel.text = it.toString() }
92+
viewModel.tryCountLabelVisible.observe { tryCountLabel.isVisible = it }
93+
viewModel.sendButtonEnable.observe { sendButton.isEnabled = it }
94+
95+
viewModel.inputCode.bindTo(codeEditText)
96+
97+
viewModel.eventDone.observe { /* close fragment */ }
98+
viewModel.eventError.observe { /* show error */ }
99+
100+
viewModel.actionOnSendCodeClick.bindOnClick(sendButton)
110101
}
111-
102+
103+
}
104+
```
105+
106+
## Есть 5 базовых объектов для взаимодействия View и ViewModel
107+
108+
### RvmState
109+
В основном предназначен для передачи состояния из **ViewModel** во **View**.
110+
- Передавать данные в **RvmState** могут только наследники **RvmPropertiesSupport**.
111+
- Всегда хранит последнее переданное значение.
112+
- Каждый подписчик в первую очередь получит последннее сохраненное значение.
113+
114+
Объявление:
115+
116+
```kotlin
117+
val state by RVM.state<DataType>(initialValue = null or data)
118+
```
119+
```kotlin
120+
val state by savedStateHandle.state<DataType>(initialValue = null or data)
121+
```
122+
123+
Подписка:
124+
125+
```kotlin
126+
viewModel.state.observe { value -> /* do */ }
127+
```
128+
129+
130+
### RvmStateProjection
131+
Почти тоже самое, что и **RvmState**, но отличается тем, что никто не может передавать данные напрямую.
132+
- Никто не может передавать данные напрямую. **RvmStateProjection** может получать данные от источников: **Observable**, **RvmState**, **RvmStateProjection**, либо объекта наследника **RvmPropertyBase** и **RvmValueProperty**.
133+
134+
Объявление:
135+
136+
```kotlin
137+
val state by RVM.state<DataType>(initialValue = null or data)
138+
val stateProjection by RVM.stateProjection(state) { /* map block */ }
139+
```
140+
```kotlin
141+
val stateProjection by RVM.stateProjectionFromSource(initialValue = null or data) { ObservableSource }
142+
```
143+
144+
Подписка:
145+
146+
```kotlin
147+
viewModel.stateProjection.observe { value -> /* do */ }
148+
```
149+
150+
### RvmEvent
151+
В основном предназначен для передачи событий или данных из **ViewModel** во **View**.
152+
- Передавать данные в **RvmEvent** могут только наследники **RvmPropertiesSupport**.
153+
- Хранит последнее переданное значение пока не появится подписчик. Только первый подписчик получит последнее сохраненное значение, все последующие подписки, будут получать только новые значения.
154+
- Пока есть активная подписка, данные не сохраняются.
155+
156+
Объявление:
157+
158+
```kotlin
159+
val event by RVM.event<DataType>()
160+
```
161+
```kotlin
162+
val event by RVM.eventNone() // for Unit Data Type
163+
```
164+
165+
Подписка:
166+
167+
```kotlin
168+
viewModel.event.observe { value -> /* do */ }
169+
```
170+
171+
### RvmConfirmationEvent
172+
Почти тоже самое, что и **RvmEvent**, но отличается тем, что хранит последнее значение пока не будет вызван метод **confirm**.
173+
- Передавать данные в **RvmConfirmationEvent** могут только наследники **RvmPropertiesSupport**.
174+
- Хранит последнее переданное значение пока не будет вызван метод **confirm**. Каждый новый подписчик будет получать последнее сохраненное значение, пока не вызван метод **confirm**.
175+
176+
Объявление:
177+
178+
```kotlin
179+
val confirmationEvent by RVM.confirmationEvent<DataType>()
180+
```
181+
```kotlin
182+
val confirmationEvent by RVM.confirmationEventNone() // for Unit Data Type
183+
```
184+
185+
Подписка:
186+
187+
```kotlin
188+
viewModel.confirmationEvent.observe { value ->
189+
/* do */
190+
viewModel.confirmationEvent.confirm()
112191
}
113192
```
114193

115-
### State
116-
**State** хранит послдение значение и излучает его при подписке. Используется для передачи значения из ViewModel в View
194+
### RvmAction
195+
В основном предназначен для передачи событий или данных из **View** во **ViewModel**.
196+
- Не хранит данные.
197+
198+
Объявление:
117199

118-
Создание
119200
```kotlin
120-
val isProgress = state<Boolean>(false)
201+
val action by RVM.action<DataType>()
121202
```
122-
Из ViewModel
123203
```kotlin
124-
isProgress.consumer.accept(true)
125-
isProgress.setValue(true) // расширение для isProgress.consumer.accept(true)
126-
isProgress.setValueIfChanged(true) // расширение для isProgress.consumer.accept(true) но с проверкой if (lastValue != newValue)
204+
val action by RVM.actionNone() // for Unit Data Type
127205
```
128-
В View
206+
207+
Привязка:
208+
129209
```kotlin
130-
isProgress.observe { value -> }
210+
viewModel.action.bindOnClick(someView)
131211
```
132212

133-
### Action
134-
**Action** ипользуется для передачи событий или параметров из View в ViewModel
213+
## Также имеется 4 вспомогательных объекта для удобной связи View-элементов с ViewModel
214+
215+
### RvmCheckControl
216+
Для связи **CompoundButton** и **ViewModel**.
217+
218+
Объявление:
135219

136-
Создание
137220
```kotlin
138-
val actionSendSmsCodeAgain = action<Unit>() // or actionEmpty() если тип Unit
221+
val checkControl by RVM.checkControl(initialChecked = false)
139222
```
140-
Из ViewModel
141223
```kotlin
142-
actionSendSmsCodeAgain.consumer.accept(Unit)
143-
actionSendSmsCodeAgain.call() // расширение для actionSendSmsCodeAgain.consumer.accept(Unit)
224+
val checkControl by savedStateHandle.checkControl(initialChecked = false)
144225
```
145-
В View
226+
227+
Привязка:
228+
146229
```kotlin
147-
actionSendSmsCodeAgain.bindOnClick(btnSendSmsCode)
148-
btnSendSmsCode.setOnClickListener { actionSendSmsCodeAgain.call() }
230+
viewModel.checkControl.bindTo(compoundButton)
149231
```
150232

151-
### Event
152-
**Event** ипользуется для передачи событий или параметров из ViewModel в View. Хранит последнее переданное значение, пока не появится подписчик.
233+
### RvmRatingControl
234+
Для связи **RatingBar** и **ViewModel**.
235+
236+
Объявление:
153237

154-
Создание
155238
```kotlin
156-
val eventDone = event<Unit>() // or eventEmpty() если тип Unit
239+
val ratingControl by RVM.ratingControl(initialValue = 3)
157240
```
158-
Из ViewModel
159241
```kotlin
160-
eventDone.consumer.accept(Unit)
161-
eventDone.call() // расширение для eventDone.consumer.accept(Unit)
242+
val ratingControl by savedStateHandle.ratingControl(initialValue = 3)
162243
```
163-
В View
244+
245+
Привязка:
246+
164247
```kotlin
165-
eventDone.observe { value -> }
248+
viewModel.ratingControl.bindTo(ratingBar)
166249
```
167250

251+
### RvmInputControl
252+
Для связи **EditText** или **TextInputLayout** и **ViewModel**.
168253

169-
## Инфо
170-
Вся либа, надстройка над LiveData. Cвойства(state, event) имею поле **liveData** для возможности совместного использования с **DataBinding**
254+
Объявление:
255+
256+
```kotlin
257+
val inputControl by RVM.inputControl(initialText = "Some Text")
258+
```
259+
```kotlin
260+
val inputControl by savedStateHandle.inputControl(initialText = "Some Text")
261+
```
262+
263+
Привязка:
264+
265+
```kotlin
266+
viewModel.inputControl.bindTo(editText)
267+
```
268+
```kotlin
269+
viewModel.inputControl.bindTo(textInputLayout)
270+
```
271+
272+
### RvmDialogControl
273+
Для связи **Dialog** и **ViewModel**.
274+
275+
Объявление:
276+
277+
```kotlin
278+
val dialogControl by RVM.dialogControl<DataType, ResultDataType>()
279+
```
280+
```kotlin
281+
val dialogControl by RVM.dialogControlDefaultResult<DataType>()
282+
```
283+
284+
Привязка:
285+
286+
```kotlin
287+
viewModel.dialogControl.bindTo { data: DataType, dc: RvmDialogControlResult<ResultDataType> ->
288+
MaterialAlertDialogBuilder(context)
289+
.title("Title")
290+
.message("Message")
291+
.setPositiveButton("OK") { _, _ -> dc.sendResult(ResultDataType) }
292+
.setNegativeButton("Cancel") { _, _ -> dc.sendResult(ResultDataType) }
293+
.create()
294+
}
295+
```

0 commit comments

Comments
 (0)