diff --git a/learning/libraries/moko/moko-units.md b/learning/legacy/moko-units.md similarity index 99% rename from learning/libraries/moko/moko-units.md rename to learning/legacy/moko-units.md index 264155958..f18e88590 100644 --- a/learning/libraries/moko/moko-units.md +++ b/learning/legacy/moko-units.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 6 --- # moko-units diff --git a/learning/libraries/moko/moko-mvvm.md b/learning/libraries/moko/moko-mvvm.md index a4c3320e0..de872548c 100644 --- a/learning/libraries/moko/moko-mvvm.md +++ b/learning/libraries/moko/moko-mvvm.md @@ -4,9 +4,134 @@ sidebar_position: 3 # moko-mvvm -Библиотека [moko-mvvm](https://github.com/icerockdev/moko-mvvm) позволяет реализовывать приложения с паттерном -Model - View - ViewModel с ViewModel в общем коде. +Библиотека [moko-mvvm](https://github.com/icerockdev/moko-mvvm) предоставляет архитектурные компоненты Model-View-ViewModel для Kotlin Multiplatform. ViewModel работает в общем коде, на Android обеспечивается lifecycle-aware поведение через интеграцию с `androidx.lifecycle`. + +## Возможности + +- **ViewModel** — хранение и управление UI и данными с `viewModelScope` (CoroutineScope, отменяемый в `onCleared`) +- **LiveData / Flow** — реактивные обёртки с операторами (`map`, `merge`, `combine`, `all`) +- **EventsDispatcher** — отправка одноразовых событий из ViewModel во View с контролем lifecycle (устаревший подход) +- **CFlow / CStateFlow / CMutableStateFlow** — обёртки над корутинными Flow, совместимые со Swift +- **ResourceState** — sealed class для состояний: `Loading`, `Success`, `Empty`, `Failed` +- **Compose Multiplatform / SwiftUI** — готовая интеграция + +## Требования + +- Android API 16+ +- iOS 11.0+ +- Gradle 6.8+ + +## Подключение + +```kotlin +// shared/build.gradle.kts +dependencies { + commonMainApi("dev.icerock.moko:mvvm-core:0.16.1") + commonMainApi("dev.icerock.moko:mvvm-flow:0.16.1") + commonMainApi("dev.icerock.moko:mvvm-state:0.16.1") + + // Compose Multiplatform + commonMainApi("dev.icerock.moko:mvvm-compose:0.16.1") + commonMainApi("dev.icerock.moko:mvvm-flow-compose:0.16.1") + + commonTestImplementation("dev.icerock.moko:mvvm-test:0.16.1") +} +``` + +Экспорт в iOS framework: + +```kotlin +kotlin { + targets.withType(KotlinNativeTarget::class.java).all { + binaries.withType(Framework::class.java).all { + export("dev.icerock.moko:mvvm-core:0.16.1") + export("dev.icerock.moko:mvvm-flow:0.16.1") + } + } +} +``` + +## Simple ViewModel — счётчик на CStateFlow + +```kotlin +// commonMain +class SimpleViewModel : ViewModel() { + private val _counter = MutableStateFlow(0) + val counter: CStateFlow = + _counter.map { it.toString() } + .stateIn(viewModelScope, SharingStarted.Lazily, "0") + .cStateFlow() + + fun onCounterButtonPressed() { + _counter.value++ + } +} +``` + +**Compose:** + +```kotlin +@Composable +fun CounterScreen( + viewModel: SimpleViewModel = getViewModel( + factory = createViewModelFactory { SimpleViewModel() } + ) +) { + val counter: String by viewModel.counter.collectAsState() + + Column { + Text(text = counter) + Button(onClick = { viewModel.onCounterButtonPressed() }) { + Text("Press me") + } + } +} +``` + +**SwiftUI:** + +```swift +struct CounterView: View { + @ObservedObject var viewModel: SimpleViewModel = SimpleViewModel() + + var body: some View { + VStack { + Text(viewModel.counter.state(\.counter)) + Button("Press me") { viewModel.onCounterButtonPressed() } + } + } +} +``` + +## ResourceState + +```kotlin +sealed class ResourceState { + class Loading : ResourceState() + data class Success(val data: T) : ResourceState() + class Empty : ResourceState() + data class Failed(val error: E) : ResourceState() +} +``` + +```kotlin +val state: CStateFlow, StringDesc>> = ... + +// Compose +when (val s = state.collectAsState().value) { + is ResourceState.Loading -> LoadingState() + is ResourceState.Success -> BookList(s.data) + is ResourceState.Empty -> EmptyState() + is ResourceState.Failed -> ErrorState(s.error) +} +``` + +## Дополнительный материал

+ +- [Референс библиотеки](https://github.com/icerockdev/moko-mvvm) +- [Семплы (DataBinding)](https://github.com/icerockdev/moko-mvvm/tree/master/sample) +- [Семплы (Compose + SwiftUI)](https://github.com/icerockdev/moko-mvvm/tree/master/sample-declarative-ui) diff --git a/learning/libraries/moko/moko-resources.md b/learning/libraries/moko/moko-resources.md index e246258bf..02c2bbc4b 100644 --- a/learning/libraries/moko/moko-resources.md +++ b/learning/libraries/moko/moko-resources.md @@ -4,9 +4,156 @@ sidebar_position: 2 # moko-resources -## Почему StringDesc не Parcelable +Библиотека [moko-resources](https://github.com/icerockdev/moko-resources) — это решение для доступа к ресурсам (строки, изображения, цвета, шрифты, файлы) из общего Kotlin Multiplatform кода. Поддерживает Android, iOS, macOS, JVM, JS/Wasm и Compose Multiplatform. -StringDesc не может быть Parcelable так как у нас есть ResourceFormattedStringDesc +## Возможности + +- **Строки и плюралы** с поддержкой локализации — общий код для всех платформ +- **StringDesc** — lifecycle-safe контейнер для строк, позволяющий отложить получение строки до момента, когда доступен platform context +- **Цвета** с поддержкой light/dark темы +- **Изображения** (SVG, PNG, JPG) с light/dark режимом +- **Шрифты** (TTF, OTF) +- **Файлы** (raw/assets) для Android +- **Compose Multiplatform** — все ресурсы доступны как `painterResource`, `stringResource`, `colorResource` и т.д. + +## Подключение + +```kotlin +// root build.gradle.kts +buildscript { + dependencies { + classpath("dev.icerock.moko:resources-generator:0.26.4") + } +} +``` + +```kotlin +// shared/build.gradle.kts +plugins { + kotlin("multiplatform") + id("dev.icerock.mobile.multiplatform-resources") +} + +dependencies { + commonMainApi("dev.icerock.moko:resources:0.26.4") + // для Compose Multiplatform: + commonMainApi("dev.icerock.moko:resources-compose:0.26.4") + commonTestImplementation("dev.icerock.moko:resources-test:0.26.4") +} + +multiplatformResources { + resourcesPackage.set("com.example.app") // обязательный package + resourcesClassName.set("SharedRes") // опционально, по умолчанию MR + resourcesVisibility.set(MRVisibility.Internal) +} +``` + +## Использование + +Вот несколько примеров, подробнее смотрите в [moko-resources](https://github.com/icerockdev/moko-resources) + +### Строки (strings.xml) + +```xml + + + Hello + Test data %d + second string %2$s first decimal %1$d + +``` + +```kotlin +// commonMain — получение StringDesc +val hello: StringDesc = MR.strings.hello.desc() +val formatted: StringDesc = MR.strings.format.format(9) +val positional: StringDesc = MR.strings.positional.format(9, "str") +``` + +```kotlin +// Android — преобразование в String +val text: String = hello.toString(context = this) + +// iOS +let text: String = hello.localized() +``` + +### Изображения + +PNG/JPG имена файлов должны содержать суффикс плотности (`@1x`, `@2x`, `@3x`, `@4x`). + +```kotlin +val image: ImageResource = MR.images.home_black_18 +val vector: ImageResource = MR.images.car_black // SVG + +// Android +imageView.setImageResource(image.drawableResId) + +// iOS +imageView.image = image.toUIImage() + +// Compose +Image(painter = painterResource(MR.images.car_black), contentDescription = null) +``` + +Поддержка Dark Mode: добавьте `-dark` к имени файла: + +- `car.svg` +- `car-dark.svg` + +### Файлы и ассеты + +```kotlin +// Чтение текстового файла +val text: String = MR.files.test_txt.readText() // common +val text: String = MR.files.test_txt.getText(context) // Android + +// Compose — реактивное чтение +val content: String? by MR.files.test_txt.readTextAsState() +``` + +## Compose Multiplatform + +Если подключён модуль `resources-compose`, все ресурсы доступны напрямую в `commonMain`: + +```kotlin +// Строки +Text(text = stringResource(MR.strings.hello_world)) + +// Плюралы +Text(text = pluralStringResource(MR.plurals.chars_count, counter, counter)) + +// Цвета +Text(color = colorResource(MR.colors.textColor), text = "Hello") + +// Изображения +Image(painter = painterResource(MR.images.moko_logo), contentDescription = null) + +// Шрифты +Text(fontFamily = fontFamilyResource(MR.fonts.cormorant_italic), text = "Hello") + +// Файлы +val fileContent: String? by MR.files.some_file_txt.readTextAsState() +``` + +## Multi-module проекты + +Если ресурсы лежат в отдельном модуле, плагин нужно применить и в модуле с ресурсами, и в модуле, который собирается в framework. + +Для вложенного модуля можно задать кастомное имя класса: + +```kotlin +multiplatformResources { + resourcesPackage.set("com.example.nested") + resourcesClassName.set("NestedMR") +} +``` + +--- + +## Почему StringDesc не Parcelable + +StringDesc не может быть Parcelable, так как у нас есть ResourceFormattedStringDesc: ```kotlin actual data class ResourceFormattedStringDesc actual constructor( @@ -23,4 +170,4 @@ actual data class ResourceFormattedStringDesc actual constructor( } ``` -Так как его аргументы типа Any, значит класс ResourceFormattedStringDesc не может быть parcelable, а значит и StringDesc не может быть Parcelable \ No newline at end of file +Так как его аргументы типа `Any`, класс `ResourceFormattedStringDesc` не может быть Parcelable, а значит и `StringDesc` не может быть Parcelable. diff --git a/university/6-lists/moko-units.md b/university/6-lists/moko-units.md index eacf3b6e1..57179e431 100644 --- a/university/6-lists/moko-units.md +++ b/university/6-lists/moko-units.md @@ -4,4 +4,4 @@ sidebar_position: 2 # moko-units -Ознакомьтесь со всеми материалами со страницы [moko-units в базе знаний](/learning/libraries/moko/moko-units/). +Ознакомьтесь со всеми материалами со страницы [moko-units в базе знаний](/learning/legacy/moko-units/).