Привет, меня зовут Саша и я Android-разработчик 🙂
(Это моя первая статья здесь и я буду рад вашей поддержке)
Разрабатывая мобильные приложения я обратил внимание что все операции которые делает приложение — это по сути запросы данных от разных источников.
Часто источниками данных являются: человек, сервер и устройство (User, Server, Platform).
Приложение принимает данные от какого-то источника, преобразует их и выдает преобразованные данные другому источнику и так пока не дойдет до корня дерева запросов.
Подобно тому как вызовы функций вкладываются друг в друга:
// псевдокод fun main() { val result = abs(min(x1, x2) + max(y1, sum(y2 + 42))) }
Можно написать вот такой код запрос-ответов:
// псевдокод fun main() { ToUser.GetChoice(MainMenu) { choice -> when (choice) { MainMenu.AddNote -> { editNote() } MainMenu.ReadNotes -> { ToPlatform.GetNotes() { notes -> readNotes(notes) } } MainMenu.SearchNotes -> { searchNotes() } MainMenu.Trash -> { ToPlatform.GetNotes() { removedNotes -> openTrash(removedNotes) } } } } }
Я решил сделать экспериментальное приложение по моей задумке и смотри что из этого получилось 🙂
Для начала я определился со стеком и целями:
✅ Стек:
Android, Kotlin, Compose, Coroutines, Room
✅ Цели:
-
Сделать приложение на Compose
-
Попрактиковаться в создании приложений на Compose
-
Написать приложение в стиле запрос-ответов (реализовать идею)
-
Написать приложение максимально просто
-
Это должно быть полноценное, функциональное приложение
-
Использовать код в качестве демонстрации, как домашнее тестовое задание
Затем написал реализацию:
Это варианты действий главного экрана, корзины и списка заметок:
enum class MainMenu(override val text: String) : IMenuItem { AddNote("Написать заметку"), ReadNotes("Читать заметки"), SearchNotes("Искать заметки"), Trash("Корзина"), } enum class TrashMenu(override val text: String) : IMenuItem { ReadNotes("Читать заметки"), SearchNotes("Искать заметки"), EmptyTrash("Очистить корзину"), } sealed class NotesChoice() : IChoice { data class Remove(val note: Note) : NotesChoice() data class Select(val note: Note) : NotesChoice() }
Вся логика приложения сводится к такому дерево-графу запросов (и к нему можно прикрутить любую UI реализацию, навигацию, платформу, сервер-клиента):
Точка входа: main()
fun main() { ToUser.GetChoice( title = "Блокнот", items = MainMenu.entries, canBack = false ).request { choice -> when (choice) { MainMenu.AddNote -> { editNote() } MainMenu.ReadNotes -> { ToPlatform.GetNotes().request { notes -> readNotes(notes) } } MainMenu.SearchNotes -> { searchNotes() } MainMenu.Trash -> { ToPlatform.GetNotes(removed = true).request { removedNotes -> openTrash(removedNotes) } } } } } fun openTrash(removedNotes: List<Note>) { // ... } fun searchNotes(removed: Boolean = false) { ToUser.GetString( title = "Поиск заметок", label = "Искать строку", actionName = "Найти", ).request { response -> when (response) { is Back -> { User.requestPrevious() } is String -> { ToPlatform.GetNotes(query = response, removed = removed).request { notes -> readNotes(notes, "Заметки по запросу \n\n\"$response\"") } } } } } fun editNote(initial: Note? = null) { // ... } fun readNotes(notes: List<Note>, title: String = "Заметки") { ToUser.GetChoice( title = title, items = notes.toMutableStateList(), ).request { response -> debug { println("choice: $response") } when (response) { is Back -> User.requestPrevious() is NotesChoice.Remove -> { response.note.removed = true ToPlatform.UpdateNote(response.note) .request { User.request.pop() ToPlatform.GetNotes().request { notes -> readNotes(notes, title) } } } is NotesChoice.Select -> editNote(response.note) } } }
Чтобы было понятнее добавил вводные:
✅ Вводные для читателя кода проекта на github:
-
handleRequestsToPlatform() — обработка запросов к платформе
-
handleRequestsToUser() — обработка запросов к пользователю
Клиенты — делаем к ним запросы и получаем от них ответы:
-
User — пользователь как клиент
-
Platform — платформа/OS как клиент
-
Backend — бэк как клиент
ToUser — запросы к пользователю:
-
PostMessage — передать сообщение пользователю
-
GetChoice — получить выбор из вариантов
-
GetString — получить текст
Вся работа корутин производится в едином скоупе приложения.
✅ Дополнительно мне удалось:
-
отказаться от использования suspend функций (они используются только там где действительно нужны, а в бизнес-логике их нет)
-
использовать минимум фреймворков (без фреймворков DI, Flow, доп. навигации, вьюмоделей)
В результате получился легковесный и полнофункциональный Блокнот с простой и наглядной бизнес-логикой.
Залил код проекта на GitHub
https://github.com/e16din/LikeNotesApp
Если чего-то не достает для ясности, задавайте мне вопросы и я с удовольствием на них отвечу 🙂
Ну и «велкам ту зе клаб» — трогайте, пробуйте на вкус, ставьте звезды проекту, делитесь с друзьями и пишите в комментариях свои мысли.
По секрету: я в открытом поиске 🙂
P.S. Пока писал статью пересмотрел код и могу сказать: мне нравится то что получилось 🙂
ссылка на оригинал статьи https://habr.com/ru/articles/942762/
Добавить комментарий