Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
---
sidebar_position: 10
sidebar_position: 7
---

# moko-kswift

Плагин устарел, используйте [https://skie.touchlab.co](https://skie.touchlab.co)

## moko-kswift

[moko-kswift](https://github.com/icerockdev/moko-kswift) - этот плагин, позволяет автоматически генерировать Swift-friendly API из общего кода:
Expand Down
2 changes: 1 addition & 1 deletion learning/legacy/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ viewModel.state.observe(viewLifecycleOwner) { state ->
### Обработка на iOS
#### moko-kswift

За счет [moko-kswift](/learning/libraries/moko/moko-kswift) у нас есть возможность использовать `sealed interface` для `State` и `Actions` из общего кода в виде `enum` в Swift, чтобы можно было обрабатывать объекты в `switch` без ветки `default`.
За счет [moko-kswift](/learning/legacy/moko-kswift) у нас есть возможность использовать `sealed interface` для `State` и `Actions` из общего кода в виде `enum` в Swift, чтобы можно было обрабатывать объекты в `switch` без ветки `default`.

Это очень полезно для обработки `Actions`, потому что при появлении нового `Action` в общем коде iOS приложение не скомпилируется из-за того, что не все объекты `enum` будут обработаны.

Expand Down
162 changes: 161 additions & 1 deletion learning/libraries/moko/moko-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,167 @@ sidebar_position: 7

# moko-errors

Библиотека [moko-errors](https://github.com/icerockdev/moko-errors) позволяет обрабатывать и показывать ошибки на экране из общего кода.
Библиотека [moko-errors](https://github.com/icerockdev/moko-errors) позволяет обрабатывать и показывать ошибки на экране из общего кода.

## ExceptionMappersStorage

`ExceptionMappersStorage` — синглтон, хранящий набор конвертеров исключений в классы ошибок, требуемые `ErrorPresenter`.

Регистрация простого маппера:

```kotlin
ExceptionMappersStorage
.register<IllegalArgumentException, StringDesc> {
"Был передан недопустимый аргумент!".desc()
}
.register<HttpException, Int> {
it.code
}
```

Регистрация маппера с условием:

```kotlin
ExceptionMappersStorage.condition<StringDesc>(
condition = { it is CustomException && it.code == 10 },
mapper = { "Произошла кастомная ошибка!".desc() }
)
```

Для каждого типа ошибки (кроме `StringDesc`, у которого уже есть значение по умолчанию) необходимо задать fallback-значение через `setFallbackValue`:

```kotlin
ExceptionMappersStorage
.setFallbackValue<Int>(520)

val throwableToIntMapper: (Throwable) -> Int =
ExceptionMappersStorage.throwableMapper()
```

Регистрацию можно выстраивать в цепочку:

```kotlin
ExceptionMappersStorage
.condition<StringDesc>(
condition = { it is CustomException && it.code == 10 },
mapper = { "Произошла кастомная ошибка!".desc() }
)
.register<IllegalArgumentException, StringDesc> {
"Был передан недопустимый аргумент!".desc()
}
.register<HttpException, Int> {
it.code
}
.setFallbackValue<Int>(520)
```

## ExceptionHandler

`ExceptionHandler` реализует безопасное выполнение кода и автоматическое отображение ошибок через `ErrorPresenter`.

Объявление в ViewModel:

```kotlin
class SimpleViewModel(
val exceptionHandler: ExceptionHandler
) : ViewModel()
```

Привязка на платформе.

На Android в Activity или Fragment:

```kotlin
viewModel.exceptionHandler.bind(
lifecycleOwner = this,
activity = this
)
```

На iOS в ViewController:

```swift
viewModel.exceptionHandler.bind(viewController: self)
```

Создание экземпляра `ExceptionHandler`:

```kotlin
val exceptionHandler = ExceptionHandler<StringDesc>(
errorPresenter = errorPresenter,
exceptionMapper = ExceptionMappersStorage.throwableMapper(),
onCatch = { println("Поймано исключение: $it") }
)
```

Использование в ViewModel:

```kotlin
fun onSendRequest() {
viewModelScope.launch {
exceptionHandler.handle {
serverRequest()
}.finally {
// код после выполнения
}.execute()
}
}
```

Можно добавлять кастомные `catch`-обработчики:

```kotlin
fun onSendRequest() {
viewModelScope.launch {
exceptionHandler.handle {
serverRequest()
}.catch<IllegalArgumentException> {
// кастомная обработка
false // true — отменяет показ ошибки; false — позволяет ErrorPresenter показать ошибку
}.execute()
}
}
```

## ErrorPresenter

Набор реализаций `ErrorPresenter`, определяющих способ отображения ошибки на платформе:

- `AlertErrorPresenter` — показывает ошибку в alert-диалоге;
- `ToastErrorPresenter` — показывает ошибку в toast на Android (на iOS — alert-диалог);
- `SnackBarErrorPresenter` — показывает ошибку в snackbar на Android (на iOS — alert-диалог);
- `SelectorErrorPresenter` — выбирает презентер по условию.

Создание презентеров в общем коде:

```kotlin
val alertErrorPresenter = AlertErrorPresenter(
alertTitle = "Ошибка".desc(),
positiveButtonText = "OK".desc()
)
val toastErrorPresenter = ToastErrorPresenter(
duration = ToastDuration.LONG
)
```

`SelectorErrorPresenter` позволяет выбрать способ показа в зависимости от типа исключения:

```kotlin
val selectorErrorPresenter = SelectorErrorPresenter { throwable ->
when (throwable) {
is CustomException -> alertErrorPresenter
else -> toastErrorPresenter
}
}

val exceptionHandler = ExceptionHandler(
errorPresenter = selectorErrorPresenter,
exceptionMapper = ExceptionMappersStorage.throwableMapper()
)
```

## Дополнительные материалы

<iframe src="//www.youtube.com/embed/scvtK62zqz8" frameborder="0" allowfullscreen width="675" height="380"></iframe>
<br/>
<br/>
Expand Down
70 changes: 64 additions & 6 deletions learning/libraries/moko/moko-fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@ sidebar_position: 6
<br/>

<iframe src="//www.youtube.com/embed/WXBbbF5pKho?list=PL6yFiPOVXVUi90sQ66dtmuXP-1-TeHwl5" frameborder="0" allowfullscreen width="675" height="380"></iframe>
<br/>


## Состав библиотеки

Библиотека состоит из нескольких модулей:
- `fields-core` — базовые классы и логика валидации
- `fields-livedata` — интеграция с `LiveData` из moko-mvvm
- `fields-flow` — интеграция с `Flow` из kotlinx.coroutines
- `fields-material` — Android View компоненты с Material Design

## FormField

Expand Down Expand Up @@ -38,7 +46,9 @@ AuthCodeContent(
...
)
```

В контенте экрана:

```kotlin
@Composable
fun AuthCodeContent(
Expand All @@ -58,18 +68,66 @@ fun AuthCodeContent(
)
...
}

```

## Валидация

Разберем, как добавлять валидацию в `FormField`:
Comment thread
RezMike marked this conversation as resolved.
- можно использовать [встроенные валидаторы](https://github.com/icerockdev/moko-fields/tree/c9c09069da717d4995ee6c96f8ec6ef7446af503/fields/src/commonMain/kotlin/dev/icerock/moko/validations)
- можно использовать [встроенные валидаторы](https://github.com/icerockdev/moko-fields/tree/master/fields-core/src/commonMain/kotlin/dev/icerock/moko/fields/core/validations)
- можно создать полностью свою валидацию

Как можно настроить валидацию:
- валидацию можно вызвать в любой момент. Зачем это?
- при первом вводе юзера - валидация не должна проверяться, пока он не закончит ввод до конца, и не нажмет кнопку, к которой будет привязана валидация, чтобы, пока он еще не ввел все, что задумал, у него не светились ошибки.
- поля можно объединить в список и валидировать их одновременно
- валидацию можно вызвать в любой момент
- Зачем? Чтобы поведение было таким: при первом вводе юзера - валидация не должна проверяться, пока он не закончит ввод до конца и не нажмет кнопку, к которой будет привязана валидация, чтобы, пока он еще не ввел все, что задумал, у него не светились ошибки.
- поля можно объединить в список и валидировать их одновременно:
```kotlin
private val fields = listOf(emailField, passwordField)

fun onSubmit() {
if (!fields.validate()) return
// данные валидны
}
```
- валидация полей может быть завязана на других полях (пароль + повторите пароль)
- у FormField есть поле `isValid` и `validationError`

## Использование с Flow

Для работы с корутинами используйте модуль `fields-flow`.

`FormField` создаётся с указанием `CoroutineScope`:
Comment thread
RezMike marked this conversation as resolved.

```kotlin
val emailField: FormField<String, StringDesc> = FormField(
scope = viewModelScope,
initialValue = "",
validation = flowBlock { email ->
ValidationResult.of(email) {
notBlank("Email не может быть пустым".desc())
matchRegex("Неверный формат".desc(), EMAIL_REGEX)
}
}
)
```

Валидация строится через `ValidationResult` — цепочкой:

```kotlin
ValidationResult.of(value)
.notBlank("Поле не может быть пустым".desc())
.matchRegex("Неверный формат".desc(), SOME_REGEX)
.validate()
```

Или через DSL, как в примере выше.

В UI данные доступны через `StateFlow`:

```kotlin
val email: String by viewModel.emailField.data.collectAsState()

TextField(
value = email,
onValueChange = { viewModel.emailField.data.value = it }
)
```
71 changes: 70 additions & 1 deletion learning/libraries/moko/moko-geo.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,73 @@ sidebar_position: 11

# moko-geo

Библиотека [moko-geo](https://github.com/icerockdev/moko-geo) - для работы с геолокацией из общего кода.
Библиотека [moko-geo](https://github.com/icerockdev/moko-geo) предоставляет возможность отслеживать геолокацию пользователя из общего кода.

## Состав библиотеки

- `geo` — базовый модуль с `LocationTracker`;
- `geo-compose` — интеграция с Compose Multiplatform.

## LocationTracker

Основной класс `LocationTracker` использует `PermissionsController` из `moko-permissions` для запроса разрешений и позволяет управлять процессом отслеживания.

### Общий код

```kotlin
class TrackerViewModel(
val locationTracker: LocationTracker
) : ViewModel() {

init {
viewModelScope.launch {
locationTracker.getLocationsFlow()
.distinctUntilChanged()
.collect { location ->
println("new location: $location")
}
}
}

fun onStartPressed() {
viewModelScope.launch { locationTracker.startTracking() }
}

fun onStopPressed() {
locationTracker.stopTracking()
}
}
```

### Android

```kotlin
val locationTracker = LocationTracker(
permissionsController = PermissionsController(applicationContext)
)
val viewModel = TrackerViewModel(locationTracker)

viewModel.locationTracker.bind(lifecycle, this, supportFragmentManager)
```

### Compose

```kotlin
val locationTracker = LocationTracker(
permissionsController = PermissionsController(applicationContext)
)
val viewModel = TrackerViewModel(locationTracker)

BindLocationTrackerEffect(locationTracker = locationTracker)
```

### iOS

```swift
let viewModel = TrackerViewModel(
locationTracker: LocationTracker(
permissionsController: PermissionsController(),
accuracy: kCLLocationAccuracyBest
)
)
```
Loading
Loading