Эта публикация является продолжением этой, поэтому чтобы быть до конца в курсе, то рекомендовал бы ознакомиться и с ней.

Декомпозиция

С выходом Jetpack Compose делать ui компоненты и, в целом, верстать стало проще. Да и сам инструмент так и навязывает переиспользовать ранее сделанные compose функции, делать их как можно меньше, чтобы можно было удобней переиспользовать.

Но что если это не всегда так удобно? Да и просто переиспользовать compose функции это слишком просто! Jetpack Compose дает куда большие возможности в вопросе декомпозиции задачи.

Простой пример: как переиспользовать compose функции, если мы строим многомодульное приложение? Нужно раскидать эти compose функции так, чтобы были доступны для разных модулей, но это все нужно организовать так, чтобы не наткнуться на circular dependency (при многомодульном построении приложения) или же чтобы это все не превратилось в "кашу" (при построении приложения в одном модуле), а также обойти кучу других проблем, которые могут возникнуть. Безусловно, решить этот пример можно множеством способов. Одно решение из множества, достаточно кардинальное, я опишу далее.

Компонента = модуль

Почему бы не выделить ui компоненту в целый модуль? И все что связано с этой компонентой будет находиться в одном модуле и относиться только к ней.

Как пример реализации, это модуль со своей compose функцией, своим паттерном, возможно даже со своей, уникальной для компоненты, логикой или даже архитектурой. Визуально это можно представить так:

У нас есть экран, с картинкой, кнопкой и тремя текстовыми полями. Экран очень простой. Декомпозировать по ui компонентам и выделить их в разные модули можно следующим образом:

Получается, что у нас экран, который сам является самостоятельным модулем, будет составляться из множества других мелких модулей, которые можно подменять будто это блоки.

Такой подход, на мой взгляд, будет приводить к тому, что разработчик будет обязан больше писать boilerplate кода и это явно не лучший выбор для "стартапа", где небольшое количество разработчиков, которым нужно решать задачи в кратчайшие сроки.
Однако, преимущества появляются для крупных команд.

Первое из преимуществ это возможность переиспользования мелких компонентов (естественно, нужно написать контракт для этой компоненты).

Вторым из преимуществ является то, что появляется возможность декомпозировать работу и нескольким разработчикам работать над одним экраном. Один разработчик делает контент BottomSheet, другой работает над кнопкой под этим BottomSheet. Каждый в своем модуле, и никто не пересекается. За исключением момента составления конечного экрана.

Третьим спорным преимуществом, но все же преимуществом, является возможность писать компоненты с использованием разных паттернов. MVI, MVVM и т.д. Какой удобней, в решении задачи, такой можно и использовать. Да в компоненте можно развернуть даже мини-архитектуру, исключительно для нее!

Но и, естественно, возникает множество вопросов, а именно: Как это организовывать? Как коммуницировать между компонентами?  и т.п. Было бы удобно, иметь уже готовый инструмент для этого или еще что-то.

Собственно, сам инструмент: Brick

Решить подобную задачу и ответить на ранее изложенные вопросы поможет навигация приложения и то как она устроена в проекте. Ранее я реализовал решение для организации навигации в приложении с использованием compose. Одной из главных идей этого решения является простота и гибкость. Придерживаясь этих же идей, я расширил свою библиотеку для навигации под решение подобного рода задач. Ей, безусловно, можно пользоваться как раньше, но иногда лучше иметь и не нуждаться, чем нуждаться и не иметь. Используя Brick программист имеет возможность логически выделить ui компоненту и подключать ее в любой экран. Причем, в логически выделенной ui компоненте, разработчик имеет возможность использовать любой подходящий паттерн для ее реализации, либо же, вообще, вокруг единственной компоненты выстроить свою архитектуру со своими слоями представления, бизнес логики и т.д.

Использовать данное решение крайне просто.

Создаем компоненту как и обычный экран.

val component1 = Component<Unit>(
    key = "CompositeScreenInternal 1",
    onCreate = { _, _ -> }, lifecycle of your componentg
    onDestroy = { _ -> },
    content = { _, _ -> Text("CompositeScreenInternal 1") }
)

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

Таким образом происходит добавление компоненты к другой компоненте, то есть самому экрану. Где “321” - это аргумент (Можно обойтись и без него, конечно).

compositeSampleRouter.attachCompositeComponent(component1, "321") //Добавление через роутер

Прикрепляем компоненту к экрану для ее отображения с помощью метода place в объекте  compositeContainer . В метод place необходимо передать ключ компоненты, которую хотим разместить внутри.

val compositeScreen = Component<Unit>(//Сам экран тоже является компонентой.
    key = "CompositeScreen",
    content = { _, compositeContainer ->
        Box(modifier = Modifier.fillMaxSize()) {
            Box(modifier = Modifier.align(Alignment.TopCenter)) {
                compositeContainer.place(component1.key)//use place method to define position of your component
            }

            Box(modifier = Modifier.align(Alignment.Center)) {
                compositeContainer.place(component3.key)
            }

            Box(modifier = Modifier.align(Alignment.BottomCenter)) {
                compositeContainer.place(component2.key)
            }
        }
    }
)

Для того, чтобы убрать компоненту необходимо использовать метод detachCompositeComponent(componentName)

Важно отметить, что метод place не размещает компоненту, а лишь говорит, что в случае если компонента с таким ключем появится в compositeContainer,  то ее нужно расположить в месте вызова метода place.

Таким образом, данное решение позволяет декомпозировать экраны вплоть до мельчайших ui компонент, переиспользовать их где нужно, выстраивать внутри них архитектуру, которая подходит в данном конкретном случае и организовывать работу в командах.

Познакомиться с решением можно здесь: Brick

Комментарии (0)