From d5ff65b9cc39077e2e259c6c3cb4b837854b5afd Mon Sep 17 00:00:00 2001 From: Anton Shestak Date: Wed, 30 Mar 2022 10:32:59 +0700 Subject: [PATCH 1/9] repository with sqldelight --- university/8-data-storage/repository.md | 183 ++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 university/8-data-storage/repository.md diff --git a/university/8-data-storage/repository.md b/university/8-data-storage/repository.md new file mode 100644 index 000000000..c4e43c3da --- /dev/null +++ b/university/8-data-storage/repository.md @@ -0,0 +1,183 @@ +--- +sidebar_position: 4 +--- + +# Реактивный источник данных + +## Проблема и решение +Почти во всех приложениях обязательно есть работа с данными, которые мы получаем от сервера, из интернета, от базы данных и т.д. +Какие-то данные будут отображаться на одном экране приложения, какие-то на двух, а какие-то на трех и более. + +Например, приложение любой социальной сети: один пост может быть репостнут разными людьми и группами. У поста есть лайки, комментарии, репосты и просмотры. +Чтобы на всех экранах отображать данные о постах в актуальном состоянии мы можем закидывать сервер запросами, однако нет гарантии, что мы нигде не ошибемся и где-нибудь не забудем добавить обновление. Из-за этого отображение поста на разных экранах будет отличаться. В любом случае, поддерживать такой проект будет очень тяжело. + +Чтобы избежать всех этих проблем нам нужно использовать такой источник данных, который бы позволил обновлять данные автоматически, а не вручную. +То, что нам нужно называется ***Реактивный источник данных*** - он выдает подписки, т.е. что-то, на что мы можем подписаться из. Благодаря этому, при любых обновлениях данных в источнике на экране они также обновятся. + +Как это представлено в проекте: +- источник данных, который ильпользует паттерн [Observer](https://ru.wikipedia.org/wiki/%D0%9D%D0%B0%D0%B1%D0%BB%D1%8E%D0%B4%D0%B0%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), например [Flow](https://developer.android.com/kotlin/flow) или [LiveData](https://developer.android.com/topic/libraries/architecture/livedata): + - база данных, которая выдает `Flow` + - репозиторий, который держит `StateFlow` или `LiveData` в оперативной памяти + - `Socket`, который выдает `Flow` +- подписываемся на `Flow` или `LiveData` у источника данных + +## Пример реализации +### Реализация источника данных +Разберем на примере [SQLDelight](https://cashapp.github.io/sqldelight/) базы данных и `Flow`. +Допустим, у нас есть экран со списком кораблей, по клику на элемент списка нужно показать детальную информацию о конкретном корабле на следующем экране. +Как вы уже знаете из [статьи](/learning/android/data-sharing#какие-данные-можно-передавать), передавать нужно идентификаторы данных, а не сами данные. +Поэтому, по клику на корабль мы будем передавать `id` этого корабля на следующий экран, а там уже обращаться в БД и получать данные интересующего нас корабля. + +Создадим таблицу, в которой будет храниться информация о корабле и запрос, позволяющий получить корабль по `id`. +Вот как будут выглядеть таблица и запрос: + +```sqldelight +CREATE TABLE ShipsTable ( + id INTEGER AS Int PRIMARY KEY, + name TEXT NOT NULL, + buildYear TEXT NOT NULL, + peopleCount INTEGER AS Int NOT NULL, + maxSpeed REAL AS Double NOT NULL, + loadCapacity INTEGER AS Int NOT NULL, + rating INTEGER AS Int NOT NULL +); + +getShipById: +SELECT * +FROM ShipsTable +WHERE id = :shipId; +``` + +`shipId` в строке `WHERE id = :shipId;` - это название аргумента, который будет принимать функция `getShipById`. +Вот какая функция будет сгенерирована: + +```kotlin +public fun getShipById(shipId: Int): Query +``` + +Названия аргументов не обязательно указывать явно - можно использовать `?`, однако это очень неудобно. +Например, у нас есть ~~очень полезная~~ функция `getShips`, позволяющая получить все корабли, если угадать и подставить подходящие аргументы :) Она принимает 3 параметра: +```sqldelight +getShips: +SELECT * +FROM ShipsTable +WHERE ? > 123 & ? < 456 & length(?) < 7; +``` +Опустим обсуждение её смысла и полезности, посмотрим на то, что будет сгенерировано в этом случае: + +```kotlin +public fun getShips( + `value`: Long, + value_: Long, + value__: Long? +): Query +``` +Согласитесь, по названиям `value` не понятно абсолютно ничего: зачем эти аргументы, за что они отвечают и тд. Еще непонятнее станет вашему коллеге, который это увидит. +Поэтому, старайтесь всегда использовать именованные аргументы. + +После создания таблицы и вызова нужной `gradle-задачи` - `generateSqlDelightInterface`, нам будем доступен для использования класс этой таблицы. + +`ShipsTable.kt`: +```kotlin +public data class ShipsTable( + public val id: Int, + public val name: String, + public val buildYear: String, + public val peopleCount: Int, + public val maxSpeed: Double, + public val loadCapacity: Int, + public val rating: Int +) { + public override fun toString(): String = """ + |ShipsTable [ + | id: $id + | name: $name + | buildYear: $buildYear + | peopleCount: $peopleCount + | maxSpeed: $maxSpeed + | loadCapacity: $loadCapacity + | rating: $rating + |] + """.trimMargin() +} +``` +Реализация метода `getShipById` будет следующей: + +```kotlin +fun getShipById(id: Int): Flow { + return shipsQueries.getShipById(id) + .asFlow() + .mapToOneOrNull() +} +``` + +В этой реализации есть одна проблема - мы работаем со сгенерированным на основе таблицы классом `ShipsTable`: +- при добавлении, удалении или изменении полей из таблицы нам может понадобиться исправлять все места, где используются объекты `ShipsTable` +- объекты `ShipsTable` будут содержать абсолютно все поля БД, которые редко будут нужны все сразу + +Чтобы избежать этих проблем, создадим свой класс `Ship`, с объектами которого мы будем работать во всем приложении. Также, добавим `extension` - `ShipsTable.toFeature()` с помощью которого будем преобразовывать объекты `ShipsTable` в `Ship`. + +`Ship.kt`: +```kotlin +data class Ship( + val id: Int, + val name: String, + val buildYear: String, + val peopleCount: Int, + val maxSpeed: Double, + val loadCapacity: Int, + val rating: Int +) +``` + +`ShipsTableMapper.kt`: +```kotlin +internal fun ShipsTable.toFeature(): Ship = Ship( + id = this.id, + name = this.name, + buildYear = this.buildYear, + peopleCount = this.peopleCount, + maxSpeed = this.maxSpeed, + loadCapacity = this.loadCapacity, + rating = this.rating +) +``` + +Обновленный метод `getShipById`: + +```kotlin +fun getShipById(id: Int): Flow { + return shipsQueries.getShipById(shipId = id) + .asFlow() + .mapToOneOrNull() + .map { it?.toFeature() } +} +``` +Такой подход позволит нам спокойно менять таблицу. После изменений нам нужно будет изменить только один метод - `toFeature()`. + +### Как подписаться на Flow + +Итак, теперь мы получаем от таблицы не просто объект `Ship`, а `Flow`, на который можем подписаться из `viewModel`: + +Вариант подписки, используя `StateFlow`: +```kotlin +val currentShip: StateFlow = repository.getShipById(id).stateIn(viewModelScope, SharingStarted.Eagerly, null ) +``` + +Вариант подписки, используя `LiveData`: +```kotlin +val currentShip: LiveData = repository.getShipById(id).asLiveData(viewModelScope, initialValue = null) +``` + +Теперь, если данные в таблице изменятся, то все методы, у которых изменилось возвращаемые значение, вызовутся еще раз. Все места в приложении, где используются данные из этого запроса, обновятся. Нигде не придется ничего вызывать и обновлять вручную. Данные изменились - `UI` сразу обновится. + +Например: в источнике данных обновился `rating` корабля с идентификатором `id` - он автоматически обновится на всех экранах, где мы его отображаем, потому что результат `repository.getShipById(id)` - это `Flow`, а мы на него подписались. + +## Практическое задание +Сделайте приложение, которое содержит: +- Реактивный репозиторий +- Источник данных для репозитория - база данных [SQLDelight](https://cashapp.github.io/sqldelight/) +- Экран со списком элементов, и двумя кнопками + - данные для списка реактивно тянутся от репозитория + - по нажатию на первую кнопку в БД добавляется еще один элемент списка + - по нажатию на вторую кнопку БД очищается From c088d8d9cfd449460a06bfb51aa53a543915ac7b Mon Sep 17 00:00:00 2001 From: Anton Shestak Date: Wed, 30 Mar 2022 10:35:39 +0700 Subject: [PATCH 2/9] add title --- university/8-data-storage/_category_.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 university/8-data-storage/_category_.json diff --git a/university/8-data-storage/_category_.json b/university/8-data-storage/_category_.json new file mode 100644 index 000000000..49994a268 --- /dev/null +++ b/university/8-data-storage/_category_.json @@ -0,0 +1,4 @@ +{ + "position": 8, + "label": "8. Хранение данных" +} From 0d91c5257a0c2830f3e2540d632d4b4dad8b79ab Mon Sep 17 00:00:00 2001 From: a6tak Date: Mon, 23 May 2022 12:35:46 +0700 Subject: [PATCH 3/9] add react repo --- university/8-data-storage/how-to-store-data.md | 16 ++++++++++++++++ university/8-data-storage/intro.md | 13 +++++++++++++ university/8-data-storage/practice.md | 14 ++++++++++++++ .../{repository.md => sqldelight.md} | 6 ++++-- university/8-data-storage/work-with-files.md | 5 +++++ 5 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 university/8-data-storage/how-to-store-data.md create mode 100644 university/8-data-storage/intro.md create mode 100644 university/8-data-storage/practice.md rename university/8-data-storage/{repository.md => sqldelight.md} (99%) create mode 100644 university/8-data-storage/work-with-files.md diff --git a/university/8-data-storage/how-to-store-data.md b/university/8-data-storage/how-to-store-data.md new file mode 100644 index 000000000..c37c719c4 --- /dev/null +++ b/university/8-data-storage/how-to-store-data.md @@ -0,0 +1,16 @@ +--- +sidebar_position: 1 +--- + +# Как хранить данные + +небольшое введение + +## Какие данные хранить в БД + +## Какие данные хранить в KeyValue +Вспомните, как мы работаем с `KeyValue` хранилищем [на проектах](../kotlin-multiplatform-mobile/multiplatform-settings#keyvaluestorage). + +## Какие данные хранить в файлах + +## Отличия Sql от NoSql \ No newline at end of file diff --git a/university/8-data-storage/intro.md b/university/8-data-storage/intro.md new file mode 100644 index 000000000..7769e1263 --- /dev/null +++ b/university/8-data-storage/intro.md @@ -0,0 +1,13 @@ +--- +sidebar_position: 0 +--- + +# Вводная +Восьмой блок курса посвящен работе с данными и их хранению в базе данных. + +Что вы узнаете: +- Какие данные стоит хранить в базе данных, а какие нет +- Чем отличаютсяя SQL базы данных от NoSQL +- Как работать с файлами в общем коде +- Как работать с Базой данных в общем коде +- Как реализовать реактивный источник данных с базой данных diff --git a/university/8-data-storage/practice.md b/university/8-data-storage/practice.md new file mode 100644 index 000000000..ae40e2b35 --- /dev/null +++ b/university/8-data-storage/practice.md @@ -0,0 +1,14 @@ +--- +sidebar_position: 4 +--- + +# Практическое задание + +Во время работы над практическим заданием настоятельно рекомендуем обращаться к разделу [Памятки для разработчика](../memos/function) + +## Функциональные требования + +## Технические требования + + +## Материалы diff --git a/university/8-data-storage/repository.md b/university/8-data-storage/sqldelight.md similarity index 99% rename from university/8-data-storage/repository.md rename to university/8-data-storage/sqldelight.md index c4e43c3da..64abe549b 100644 --- a/university/8-data-storage/repository.md +++ b/university/8-data-storage/sqldelight.md @@ -1,8 +1,10 @@ --- -sidebar_position: 4 +sidebar_position: 2 --- -# Реактивный источник данных +# SqlDelight + +## Реактивный источник данных ## Проблема и решение Почти во всех приложениях обязательно есть работа с данными, которые мы получаем от сервера, из интернета, от базы данных и т.д. diff --git a/university/8-data-storage/work-with-files.md b/university/8-data-storage/work-with-files.md new file mode 100644 index 000000000..e41e05224 --- /dev/null +++ b/university/8-data-storage/work-with-files.md @@ -0,0 +1,5 @@ +--- +sidebar_position: 3 +--- + +# Работа с файлами From baa75bb4953b4700ac967a1ee85c2ef861b1ab0e Mon Sep 17 00:00:00 2001 From: a6tak Date: Mon, 23 May 2022 13:46:37 +0700 Subject: [PATCH 4/9] practice --- university/8-data-storage/practice.md | 13 ++++++++++- university/8-data-storage/sqldelight.md | 30 +++++-------------------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/university/8-data-storage/practice.md b/university/8-data-storage/practice.md index ae40e2b35..8785967cd 100644 --- a/university/8-data-storage/practice.md +++ b/university/8-data-storage/practice.md @@ -3,12 +3,23 @@ sidebar_position: 4 --- # Практическое задание +В этом прак +Добавьте в ваше приложение реактивный источник данных, используя базу данных SQLDelight, храните в ней данные о репозиториях. Во время работы над практическим заданием настоятельно рекомендуем обращаться к разделу [Памятки для разработчика](../memos/function) ## Функциональные требования +- Данные о репозиториях должны храниться в базе данных +- Данные о репозиториях должны реактивно обновляться +- Обновлять данные о репозитории раз в N секунд, все остальное время брать из БД +- Вынести работу с файлами(добавление картинки к issue) в общий код ## Технические требования - +1. Использовать `SQLDelight` базу данных +2. Использовать `okio` для работы с файлами в общем коде ## Материалы +[okio](https://github.com/square/okio) +[Документация](https://square.github.io/okio/3.x/okio/okio/okio/) okio +[SQLDelight](https://cashapp.github.io/sqldelight/) +[SQLDelight Getting Started with Multiplatform](https://cashapp.github.io/sqldelight/multiplatform_sqlite/) diff --git a/university/8-data-storage/sqldelight.md b/university/8-data-storage/sqldelight.md index 64abe549b..a751d72ee 100644 --- a/university/8-data-storage/sqldelight.md +++ b/university/8-data-storage/sqldelight.md @@ -4,28 +4,14 @@ sidebar_position: 2 # SqlDelight -## Реактивный источник данных +## Реактивный источник данных c SQLDelight -## Проблема и решение -Почти во всех приложениях обязательно есть работа с данными, которые мы получаем от сервера, из интернета, от базы данных и т.д. -Какие-то данные будут отображаться на одном экране приложения, какие-то на двух, а какие-то на трех и более. +Для начала, вспомните что такое [реактивный источник данных](../icerock-basics/repository#проблема-и-решение) -Например, приложение любой социальной сети: один пост может быть репостнут разными людьми и группами. У поста есть лайки, комментарии, репосты и просмотры. -Чтобы на всех экранах отображать данные о постах в актуальном состоянии мы можем закидывать сервер запросами, однако нет гарантии, что мы нигде не ошибемся и где-нибудь не забудем добавить обновление. Из-за этого отображение поста на разных экранах будет отличаться. В любом случае, поддерживать такой проект будет очень тяжело. +Сейчас мы разберем, как реализовать реактивный источник данных, используя [SQLDelight](https://cashapp.github.io/sqldelight/) базу данных и `Flow`. -Чтобы избежать всех этих проблем нам нужно использовать такой источник данных, который бы позволил обновлять данные автоматически, а не вручную. -То, что нам нужно называется ***Реактивный источник данных*** - он выдает подписки, т.е. что-то, на что мы можем подписаться из. Благодаря этому, при любых обновлениях данных в источнике на экране они также обновятся. - -Как это представлено в проекте: -- источник данных, который ильпользует паттерн [Observer](https://ru.wikipedia.org/wiki/%D0%9D%D0%B0%D0%B1%D0%BB%D1%8E%D0%B4%D0%B0%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), например [Flow](https://developer.android.com/kotlin/flow) или [LiveData](https://developer.android.com/topic/libraries/architecture/livedata): - - база данных, которая выдает `Flow` - - репозиторий, который держит `StateFlow` или `LiveData` в оперативной памяти - - `Socket`, который выдает `Flow` -- подписываемся на `Flow` или `LiveData` у источника данных - -## Пример реализации ### Реализация источника данных -Разберем на примере [SQLDelight](https://cashapp.github.io/sqldelight/) базы данных и `Flow`. + Допустим, у нас есть экран со списком кораблей, по клику на элемент списка нужно показать детальную информацию о конкретном корабле на следующем экране. Как вы уже знаете из [статьи](/learning/android/data-sharing#какие-данные-можно-передавать), передавать нужно идентификаторы данных, а не сами данные. Поэтому, по клику на корабль мы будем передавать `id` этого корабля на следующий экран, а там уже обращаться в БД и получать данные интересующего нас корабля. @@ -176,10 +162,4 @@ val currentShip: LiveData = repository.getShipById(id).asLiveData(viewMod Например: в источнике данных обновился `rating` корабля с идентификатором `id` - он автоматически обновится на всех экранах, где мы его отображаем, потому что результат `repository.getShipById(id)` - это `Flow`, а мы на него подписались. ## Практическое задание -Сделайте приложение, которое содержит: -- Реактивный репозиторий -- Источник данных для репозитория - база данных [SQLDelight](https://cashapp.github.io/sqldelight/) -- Экран со списком элементов, и двумя кнопками - - данные для списка реактивно тянутся от репозитория - - по нажатию на первую кнопку в БД добавляется еще один элемент списка - - по нажатию на вторую кнопку БД очищается +Подключите базу данных [SQLDelight](https://cashapp.github.io/sqldelight/) к вашему приложению From 89984e327dc7cdbec6b1cc5a48a01fddf25b5a2c Mon Sep 17 00:00:00 2001 From: a6tak Date: Mon, 23 May 2022 17:42:20 +0700 Subject: [PATCH 5/9] first iteration --- .../8-data-storage/how-to-store-data.md | 29 +++++++++++++++++-- university/8-data-storage/intro.md | 4 +-- university/8-data-storage/practice.md | 9 +++--- university/8-data-storage/sqldelight.md | 2 +- university/8-data-storage/work-with-files.md | 8 +++++ 5 files changed, 42 insertions(+), 10 deletions(-) diff --git a/university/8-data-storage/how-to-store-data.md b/university/8-data-storage/how-to-store-data.md index c37c719c4..c0e9b7643 100644 --- a/university/8-data-storage/how-to-store-data.md +++ b/university/8-data-storage/how-to-store-data.md @@ -4,13 +4,38 @@ sidebar_position: 1 # Как хранить данные -небольшое введение +В мобильном приложении может понадобиться работать с различными данными, которые нужно где-то хранить, например: +- код-пароль для входа в приложение +- файлы, которые юзер скачал через приложение +- структуры, которые мы можем получать от сервера, которые обновляются очень редко +- и тд + +Все эти данные следует хранить по разному, сейчас разберемся как. ## Какие данные хранить в БД +Как вы знаете, база данных представляет из себя набор таблиц, в элементах которых как раз и находятся те данные, которые мы сохранили в БД. +Важно понимать, что в базе данных нужно хранить именно повторяющиеся структуры, которые использует приложение, например: +- объект `Order` для приложения-ресторана, чтобы собирать статистику по количеству заказов в месяц +- объект `News` для отображения новостей в социальных сетях, чтобы даже при отсутствии интернета юзеру можно было показать прошлые новости +- объект `Message` - сообщение в чате, чтобы также при отсутствии интернета юзер смог прочитать прошлые сообщения + +В базе данных не стоит хранить файлы (картинки, электронные книги, документы и тд) потому что это, во-первых, неудобно, а во-вторых - пользователь может захотеть использовать этот файл в другом приложении, но такой возможности у него не будет. +Однако, в базу данных можно сохранить путь до этих файлов. ## Какие данные хранить в KeyValue Вспомните, как мы работаем с `KeyValue` хранилищем [на проектах](../kotlin-multiplatform-mobile/multiplatform-settings#keyvaluestorage). +Предназначение KeyValue хранилища в том, чтобы хранить там именно уникальные данные, которые требуются для входа или авторизации, а не контент приложения, например: +- код-пароль, который юзер задал при первом входе в приложение +- email и пароль, чтобы при входе не вводить их каждый раз вручную +- ***сюда бы еще примеров*** + +Также, в KeyValue не должно быть коллекций и списков, потому что множества нужно хранить в БД. + ## Какие данные хранить в файлах -## Отличия Sql от NoSql \ No newline at end of file +Такие файлы, как картинки, электронные книги, документы и другие, которые можно скачать при помощи приложения нужно сохранять именно в виде файлов, потому что тогда у юзера будет возможность использовать их вне приложения, что изначально и подразумевается. + +## Отличия Sql от NoSql +Прочитайте [статью](https://smoff.ru/howitworks/otlichiya-sql-nosql) про разницу между SQL и NoSQL базами данных. +Пример реляционной базы данных - [SQLDelight](https://cashapp.github.io/sqldelight/multiplatform_sqlite/), нереляционной - [MongoDB](https://www.mongodb.com/docs/realm/get-started/introduction-mobile/). diff --git a/university/8-data-storage/intro.md b/university/8-data-storage/intro.md index 7769e1263..3807903f0 100644 --- a/university/8-data-storage/intro.md +++ b/university/8-data-storage/intro.md @@ -7,7 +7,7 @@ sidebar_position: 0 Что вы узнаете: - Какие данные стоит хранить в базе данных, а какие нет -- Чем отличаютсяя SQL базы данных от NoSQL +- Чем отличаются SQL и NoSQL базы данных - Как работать с файлами в общем коде -- Как работать с Базой данных в общем коде +- Как работать с базой данных в общем коде - Как реализовать реактивный источник данных с базой данных diff --git a/university/8-data-storage/practice.md b/university/8-data-storage/practice.md index 8785967cd..123148994 100644 --- a/university/8-data-storage/practice.md +++ b/university/8-data-storage/practice.md @@ -3,7 +3,6 @@ sidebar_position: 4 --- # Практическое задание -В этом прак Добавьте в ваше приложение реактивный источник данных, используя базу данных SQLDelight, храните в ней данные о репозиториях. Во время работы над практическим заданием настоятельно рекомендуем обращаться к разделу [Памятки для разработчика](../memos/function) @@ -19,7 +18,7 @@ sidebar_position: 4 2. Использовать `okio` для работы с файлами в общем коде ## Материалы -[okio](https://github.com/square/okio) -[Документация](https://square.github.io/okio/3.x/okio/okio/okio/) okio -[SQLDelight](https://cashapp.github.io/sqldelight/) -[SQLDelight Getting Started with Multiplatform](https://cashapp.github.io/sqldelight/multiplatform_sqlite/) +1. [okio](https://github.com/square/okio) +2. [Документация](https://square.github.io/okio/3.x/okio/okio/okio/) okio +3. [SQLDelight](https://cashapp.github.io/sqldelight/) +4. SQLDelight [Getting Started with Multiplatform](https://cashapp.github.io/sqldelight/multiplatform_sqlite/) diff --git a/university/8-data-storage/sqldelight.md b/university/8-data-storage/sqldelight.md index a751d72ee..b26555645 100644 --- a/university/8-data-storage/sqldelight.md +++ b/university/8-data-storage/sqldelight.md @@ -162,4 +162,4 @@ val currentShip: LiveData = repository.getShipById(id).asLiveData(viewMod Например: в источнике данных обновился `rating` корабля с идентификатором `id` - он автоматически обновится на всех экранах, где мы его отображаем, потому что результат `repository.getShipById(id)` - это `Flow`, а мы на него подписались. ## Практическое задание -Подключите базу данных [SQLDelight](https://cashapp.github.io/sqldelight/) к вашему приложению +Подключите базу данных [SQLDelight](https://cashapp.github.io/sqldelight/) к вашему приложению. diff --git a/university/8-data-storage/work-with-files.md b/university/8-data-storage/work-with-files.md index e41e05224..cba75f8a6 100644 --- a/university/8-data-storage/work-with-files.md +++ b/university/8-data-storage/work-with-files.md @@ -3,3 +3,11 @@ sidebar_position: 3 --- # Работа с файлами + +## okio + +Для работы с файлами в общем коде мы будем пользоваться библиотекой [okio](https://square.github.io/okio/). +Изучите документацию по [подключению](https://square.github.io/okio/multiplatform/#gradle-configuration) и [использованию](https://square.github.io/okio/file_system/) `okio` в общем коде. + +## Практическое задание +Вынесите логику работы с файлами в общий код, используя библиотеку `okio`. From ee1952b0f10a18a96041c9e8a8cbf50e40d95792 Mon Sep 17 00:00:00 2001 From: a6tak Date: Tue, 24 May 2022 12:43:18 +0700 Subject: [PATCH 6/9] fixes after review --- .../8-data-storage/how-to-store-data.md | 34 +++++++++++++------ university/8-data-storage/practice.md | 11 ++++-- university/8-data-storage/sqldelight.md | 22 ++++++++++-- university/8-data-storage/work-with-files.md | 2 +- 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/university/8-data-storage/how-to-store-data.md b/university/8-data-storage/how-to-store-data.md index c0e9b7643..6bb458f2b 100644 --- a/university/8-data-storage/how-to-store-data.md +++ b/university/8-data-storage/how-to-store-data.md @@ -5,9 +5,12 @@ sidebar_position: 1 # Как хранить данные В мобильном приложении может понадобиться работать с различными данными, которые нужно где-то хранить, например: -- код-пароль для входа в приложение +- код-пароль для входа в приложение (разумеется не в чистом виде) - файлы, которые юзер скачал через приложение -- структуры, которые мы можем получать от сервера, которые обновляются очень редко +- коллекции однотипных элементов + - новости + - сообщения + - посты в ленте - и тд Все эти данные следует хранить по разному, сейчас разберемся как. @@ -19,23 +22,32 @@ sidebar_position: 1 - объект `News` для отображения новостей в социальных сетях, чтобы даже при отсутствии интернета юзеру можно было показать прошлые новости - объект `Message` - сообщение в чате, чтобы также при отсутствии интернета юзер смог прочитать прошлые сообщения -В базе данных не стоит хранить файлы (картинки, электронные книги, документы и тд) потому что это, во-первых, неудобно, а во-вторых - пользователь может захотеть использовать этот файл в другом приложении, но такой возможности у него не будет. -Однако, в базу данных можно сохранить путь до этих файлов. +В базе данных не стоит хранить файлы (картинки, электронные книги, документы и тд) потому что она не предназначена для хранения файлов. +Основная цель БД - организовать быстрый доступ к конкретным записям и возможность поиска по этим записям. + +Если мы будем хранить в БД большой файл, то, во-первых, придется получать файлы из базы данных, что в принципе противоречит ее назначению, так еще и будет сильно тормозить ее работу. +Во-вторых - сортировать по бинарному файлу у нас не будет абсолютно никакой возможности. +Ну и в-третьих, незачем изобретать велосипед, когда для хранения файлов есть файловая система. + +Однако, мы можете хранить в базе данных пути до этих файлов, либо же их названия. ## Какие данные хранить в KeyValue Вспомните, как мы работаем с `KeyValue` хранилищем [на проектах](../kotlin-multiplatform-mobile/multiplatform-settings#keyvaluestorage). -Предназначение KeyValue хранилища в том, чтобы хранить там именно уникальные данные, которые требуются для входа или авторизации, а не контент приложения, например: +Предназначение `KeyValue` хранилища в том, чтобы хранить там только те данные, которые в конкретный момент времени могут быть только в единственном экземпляре, например: - код-пароль, который юзер задал при первом входе в приложение -- email и пароль, чтобы при входе не вводить их каждый раз вручную -- ***сюда бы еще примеров*** +- email и пароль +- id текущего заказа +- и тд. -Также, в KeyValue не должно быть коллекций и списков, потому что множества нужно хранить в БД. +Например, мы хотим знать id текущего заказа. Для этого мы добавили столбец в БД - `isCurrentOrder`. Во время разработки логики мы ошиблись и в какой-то момент у нас оказалось две записи в БД с isCurrentOrder = true - ошибка. +Поэтому, вместо БД для таких флагов мы будем использовать хранилище `KeyValue`, которое гарантирует единственность значения по конкретному ключу. -## Какие данные хранить в файлах +Также, в `KeyValue` не должно быть коллекций и списков, потому что множества нужно хранить в БД. -Такие файлы, как картинки, электронные книги, документы и другие, которые можно скачать при помощи приложения нужно сохранять именно в виде файлов, потому что тогда у юзера будет возможность использовать их вне приложения, что изначально и подразумевается. +## Какие данные хранить в файлах +Большие файлы должны сохраняться как файлы, чтобы их можно было читать и записывать потоково, чтобы не забивать оперативную память устройства. Читать и записывать потоково в БД мы не можем, а в файл - можем. ## Отличия Sql от NoSql Прочитайте [статью](https://smoff.ru/howitworks/otlichiya-sql-nosql) про разницу между SQL и NoSQL базами данных. -Пример реляционной базы данных - [SQLDelight](https://cashapp.github.io/sqldelight/multiplatform_sqlite/), нереляционной - [MongoDB](https://www.mongodb.com/docs/realm/get-started/introduction-mobile/). +Пример реляционной базы данных - [SQLDelight](https://cashapp.github.io/sqldelight/multiplatform_sqlite/), нереляционной - [GitHub](https://github.com/realm/realm-kotlin) [MongoDB Realm](https://www.mongodb.com/docs/realm/get-started/introduction-mobile/). diff --git a/university/8-data-storage/practice.md b/university/8-data-storage/practice.md index 123148994..a4ebcf208 100644 --- a/university/8-data-storage/practice.md +++ b/university/8-data-storage/practice.md @@ -10,15 +10,20 @@ sidebar_position: 4 ## Функциональные требования - Данные о репозиториях должны храниться в базе данных - Данные о репозиториях должны реактивно обновляться -- Обновлять данные о репозитории раз в N секунд, все остальное время брать из БД - Вынести работу с файлами(добавление картинки к issue) в общий код +- Возможность сделать pull-to-refresh на экране детального вида репозитория ## Технические требования 1. Использовать `SQLDelight` базу данных -2. Использовать `okio` для работы с файлами в общем коде +2. Использовать миграции для обновления структуры базы данных +3. Использовать `okio` для работы с файлами в общем коде + +## Test cases +На экране детального просмотра репозитория видим N звездочек. Заходим на сайт GitHub - ставим там этому репозиторию звездочку, делаем на экране pull-to-refresh - видим N+1 звездочек. Возвращаемся на экран списка репозиториев - там количество звездочек также обновилось. ## Материалы 1. [okio](https://github.com/square/okio) 2. [Документация](https://square.github.io/okio/3.x/okio/okio/okio/) okio 3. [SQLDelight](https://cashapp.github.io/sqldelight/) -4. SQLDelight [Getting Started with Multiplatform](https://cashapp.github.io/sqldelight/multiplatform_sqlite/) +4. [SQLDelight Getting Started with Multiplatform](https://cashapp.github.io/sqldelight/multiplatform_sqlite/) +5. [SQLDelight Migrations](https://cashapp.github.io/sqldelight/jvm_sqlite/migrations/) diff --git a/university/8-data-storage/sqldelight.md b/university/8-data-storage/sqldelight.md index b26555645..fe912c6fb 100644 --- a/university/8-data-storage/sqldelight.md +++ b/university/8-data-storage/sqldelight.md @@ -2,7 +2,17 @@ sidebar_position: 2 --- -# SqlDelight +# SQLDelight + +## SQLDelight + +[SQLDelight](https://cashapp.github.io/sqldelight/multiplatform_sqlite/) - это библиотека для удобной работы с БД в общем коде. Она позволяет генерировать классы и методы для работы с базой данных. + +Статьи для пошаговой настройки библиотеки: +- [Configuring SQLDelight and implementing cache logic](https://play.kotlinlang.org/hands-on/Networking%20and%20Data%20Storage%20with%20Kotlin%20Multiplatfrom%20Mobile/05_Configuring_SQLDelight_an_implementing_cache) от Kotlin +- [Настройка SQLDelight для хранения данных](https://runebook.dev/ru/docs/kotlin/docs/kmm-configure-sqldelight-for-data-storage) + +Изучите [что такое миграции](https://ru.stackoverflow.com/questions/325882/%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BD%D1%83%D0%B6%D0%BD%D1%8B-%D0%BC%D0%B8%D0%B3%D1%80%D0%B0%D1%86%D0%B8%D0%B8) и [прочитайте](https://cashapp.github.io/sqldelight/jvm_sqlite/migrations/) как их создавать в SQLDelight. ## Реактивный источник данных c SQLDelight @@ -162,4 +172,12 @@ val currentShip: LiveData = repository.getShipById(id).asLiveData(viewMod Например: в источнике данных обновился `rating` корабля с идентификатором `id` - он автоматически обновится на всех экранах, где мы его отображаем, потому что результат `repository.getShipById(id)` - это `Flow`, а мы на него подписались. ## Практическое задание -Подключите базу данных [SQLDelight](https://cashapp.github.io/sqldelight/) к вашему приложению. +Подключите базу данных [SQLDelight](https://cashapp.github.io/sqldelight/multiplatform_sqlite/) к вашему приложению, выполните следующие условия: + - Создание БД должно происходить в `SharedFactory` + - Доступ к БД должен быть только у репозитория + - Создайте таблицу - `RepoTable` с двумя столбцами: id и testMessage + - Создайте метод для добавления записи в БД + - Создайте метод для получения всех записей в БД + - Протестируйте работоспособность вашей БД + +Главно - чтобы проект запустился и заработал на обеих платформах, дальше в практике мы заполним БД. diff --git a/university/8-data-storage/work-with-files.md b/university/8-data-storage/work-with-files.md index cba75f8a6..f30a8c70a 100644 --- a/university/8-data-storage/work-with-files.md +++ b/university/8-data-storage/work-with-files.md @@ -10,4 +10,4 @@ sidebar_position: 3 Изучите документацию по [подключению](https://square.github.io/okio/multiplatform/#gradle-configuration) и [использованию](https://square.github.io/okio/file_system/) `okio` в общем коде. ## Практическое задание -Вынесите логику работы с файлами в общий код, используя библиотеку `okio`. +Вынесите логику работы с файлами (для добавления картинки к issue) в общий код, используя библиотеку `okio`. From 1bfebd054fa7c55c7b66ff33ae5ced6cb6131a9c Mon Sep 17 00:00:00 2001 From: a6tak Date: Tue, 24 May 2022 18:04:57 +0700 Subject: [PATCH 7/9] remove link --- university/4-icerock-basics/ui-units.md | 1 - 1 file changed, 1 deletion(-) diff --git a/university/4-icerock-basics/ui-units.md b/university/4-icerock-basics/ui-units.md index a72ac0e41..7f10c03fa 100644 --- a/university/4-icerock-basics/ui-units.md +++ b/university/4-icerock-basics/ui-units.md @@ -32,7 +32,6 @@ sidebar_position: 6 Посмотрите видео материал с подробным пояснением причин обобщения реализации между платформами и принципами работы. -После ознакомления с видео материалом - дополните свои знания материалами со страницы [moko-units в базе знаний](../../learning/libraries/moko/moko-units/). ## Практическая задача From 712a187b1bca14b0b069a2a033433d86dfddf64f Mon Sep 17 00:00:00 2001 From: a6tak Date: Wed, 25 May 2022 09:57:15 +0700 Subject: [PATCH 8/9] fixes --- university/8-data-storage/how-to-store-data.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/university/8-data-storage/how-to-store-data.md b/university/8-data-storage/how-to-store-data.md index 6bb458f2b..19c0ceb87 100644 --- a/university/8-data-storage/how-to-store-data.md +++ b/university/8-data-storage/how-to-store-data.md @@ -49,5 +49,5 @@ sidebar_position: 1 Большие файлы должны сохраняться как файлы, чтобы их можно было читать и записывать потоково, чтобы не забивать оперативную память устройства. Читать и записывать потоково в БД мы не можем, а в файл - можем. ## Отличия Sql от NoSql -Прочитайте [статью](https://smoff.ru/howitworks/otlichiya-sql-nosql) про разницу между SQL и NoSQL базами данных. -Пример реляционной базы данных - [SQLDelight](https://cashapp.github.io/sqldelight/multiplatform_sqlite/), нереляционной - [GitHub](https://github.com/realm/realm-kotlin) [MongoDB Realm](https://www.mongodb.com/docs/realm/get-started/introduction-mobile/). +Прочитайте [статью](https://smoff.ru/howitworks/otlichiya-sql-nosql) про разницу между SQL и NoSQL базами данных. +Пример реляционной базы данных - [SQLDelight](https://cashapp.github.io/sqldelight/multiplatform_sqlite/), нереляционной - [MongoDB Realm](https://www.mongodb.com/docs/realm/get-started/introduction-mobile/) ([описание](https://github.com/realm/realm-kotlin)) From e357ee80ccddfc452ff6345d18d3b7878ffde239 Mon Sep 17 00:00:00 2001 From: Aleksey Mikhailov Date: Wed, 25 May 2022 10:04:11 +0700 Subject: [PATCH 9/9] Update how-to-store-data.md --- university/8-data-storage/how-to-store-data.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/university/8-data-storage/how-to-store-data.md b/university/8-data-storage/how-to-store-data.md index 19c0ceb87..ab2c068ef 100644 --- a/university/8-data-storage/how-to-store-data.md +++ b/university/8-data-storage/how-to-store-data.md @@ -50,4 +50,4 @@ sidebar_position: 1 ## Отличия Sql от NoSql Прочитайте [статью](https://smoff.ru/howitworks/otlichiya-sql-nosql) про разницу между SQL и NoSQL базами данных. -Пример реляционной базы данных - [SQLDelight](https://cashapp.github.io/sqldelight/multiplatform_sqlite/), нереляционной - [MongoDB Realm](https://www.mongodb.com/docs/realm/get-started/introduction-mobile/) ([описание](https://github.com/realm/realm-kotlin)) +Пример реляционной базы данных - [SQLDelight](https://github.com/cashapp/sqldelight/) ([описание](https://cashapp.github.io/sqldelight/multiplatform_sqlite/)), нереляционной - [MongoDB Realm](https://github.com/realm/realm-kotlin) ([описание](https://www.mongodb.com/docs/realm/get-started/introduction-mobile/))