Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#29 units additional #53

Merged
merged 2 commits into from
Sep 15, 2021
Merged
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
188 changes: 188 additions & 0 deletions learning/libraries/moko/moko-units.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,94 @@
# moko-units

Это библиотека для управления списками и коллекциями из общего кода приложения.

## Зачем нужна UnitFactory

Иницилизация всех view-моделей происходит на нативной стороне через нашу SharedFactory, которая несет в себе методы для создания фабрик фичей.

Например, iOS испльзует TableView и CollectionView для отображения набора данных, а Android - RecyclerView.
Следовательно, каждая платформа требует конкретной реализации табличных данных, зависящей от единной модели.

Модель ячейки как раз и описывает конкретный метод из нашего интерфейса.

Для лучшего понимания рассмотрим простой пример:
```kotlin
/*
* ListUnitFactory.kt
*/

// Интерфейс создания юнитов для таблицы с однотипными ячейками
interface ListUnitFactory {
// предопределение метода создания ячейки с текстом
fun createItem(
text: String
itemId: Int
): TableUnitItem
}
```

В свою очередь методы этой фабрики нам нужно использовать внутри view-модели, для создания модели самой таблицы:

```kotlin
/*
* ListViewModel.kt
*/

val units: List<TableUnitItem> = listOf(
unitFactory.createItem(text = "Start use Units", itemId = 0),
unitFactory.createItem(text = "moko-units =)", itemId = 1),
unitFactory.createItem(text = "Hello Units!", itemId = 2),
unitFactory.createItem(text = "Wooooww!", itemId = 3)
)
```

А сама реализация создания "юнитов" происходит на платформе при помощи вспомогательных классов:

iOS:

```swift
/*
* ListUnitFactoryImpl.swift
*/

import UIKit
import MultiPlatformLibrary
import MultiPlatformLibraryUnits

// реализация интерфейса из общей логики
class ListUnitFactoryImpl: ListUnitFactory {
func createItem(text: String, itemId: Int32) -> TableUnitItem {
// использования класса UITableViewCellUnit из пода MultiPlatformLibraryUnits
return UITableViewCellUnit<TableViewCell>(
data: .init(text: text),
itemId: Int64(itemId)
)
}
}
```

Android:

```kotlin
/*
* ListUnitFactoryImpl.kt
*/

class ListUnitFactoryImpl: ListUnitFactory {
override fun fun createItem(
text: String,
itemId: Int
): TableUnitItem = ListItem(
text: text
itemId: itemId
)
// в данном случае перевод модели во view элемент будет проиходить
// не внутри реализации фабрики юнитов
}
```

Из всего вышесказанного можно сделать вывод, что нам необходима UnitFactory, чтобы создавать специфичные для платформы элементы с общей моделью, с возможностью управления ими из общей логики.

## Можно ли передавать лямбду в UnitItem?

Да (с использованием `weakLambda`), но нужно следить за цикличными ссылками, иначе будет утечка
Expand Down Expand Up @@ -82,3 +171,102 @@ class MyViewModelUnitsFactoryImpl(private val context: Context) : MyViewModelUni
и `bindViewHolder`) и полученный Context не должен сохраняться куда либо (его использование должно
ограничиваться данными методами UnitItem'а).


## itemId - как и зачем?

Для коректного отображения нестатичных данных каждый элемент должен иметь свой уникальный
идентификатор, зависящий от ключевых (неизменяемых) значений его сигнатуры. Иначе же при изменении модели ячейки, ее идентификатор будет меняться, что приведет к полному пересозданию ячейки, а не ее изменению.
Давайте разберем, что здесь написано на небольшом примере:

Пусть у нас есть эндпоинт `/news` нашего API для доступа к списку новостей, возвращаемый данные в некоторой сигнатуре. Необходимо создать таблицу для отображения этих данных.

Json ответ:

```json
{
"news": [
{
"id": 1, // идентификатор
"title": "Release Moko-Units!!!", // заголовок новости
"description": "Wow , IceRock =)", // текст новости
"views": 11 // количество просмотров,
// меняется когда просматривают запись на сайте, например
}
]
}
```

В таком случае уникальный номер записи приходит с сервера, и мы спокойно может использовать его как itemId
конкретного юнита в нашей таблице. И при повторном запросе, при котором вполне может поменяться
количество просмотром данной записи, у нас произойдет изменение ячейки по ее идентификатору, а не полное ее пересоздание.

> Что делать, если данные приходят без идентификатора?

В таком случае можно самостоятельно выделить из модели данных ключевой элемент:

```json
{
"customers": [
{
"firstName": "Ivan",
"lastName": "Ivanov",
"phone": "+79999999999", // ключевое значение
"ordersCount": 12
// ...
},
// ...
]
}
```

в последствии они будут переведены в data-класс:
```kotlin
data class CustomerModel(
val firstName: String,
val lastName: String,
val phone: String,
val ordersCount: Int,
// ...
)
```

по ключевому значению которого можно взять хеш:

```kotlin

interface ListUnitFactory {
// предопределение метода создания ячейки с текстом
fun createCustomerItem(
model: CustomerModel
itemId: Int
): TableUnitItem
}

// ...
private val customers: MutableLiveData<List<CustomerModel>> = MutableLiveData(
listOf(
CustomerModel(
firstName = "Ivan",
lastName = "Ivanov",
phone = "89999999999"
),
// ...
)
)

val units: LiveData<List<TableUnitItem>> = items.map { items ->
items.map {
unitFactory.createCustomerItem(
model = it,
itemId = it.phone.hashCode()
)
}
}
```

Из-за использования хеш-функций мы снижаем вероятность коллизий
в идентификаторах ячеек нашей таблицы.

## Материалы

- [Репозиторий библиотеки](https://github.com/icerockdev/moko-units)