Начну с того, что здесь нет однозначного победителя и единственного “лучшего” решения. Несколько решений выглядят очень богатыми по возможностям, и у каждого своя философия. Нельзя сказать, что существует лучшая архитектурная библиотека, которую все “должны” использовать, - всё зависит от нужд команды.

Я бы всегда советовал выбирать лучшее техническое решение под бизнес‑задачи, а не наоборот - т.е. не оптимизировать под новизну, “крутизну” технологии, возможности или свой личный интерес к какой‑то технологии. Бизнес и потребности команды всегда должны быть главным фактором при выборе технического решения.

Годы подряд, когда мне нужно было выбрать конкретную зависимость, я чувствовал, что нет “единого источника правды”, который бы сравнивал как можно больше библиотек и решений по как можно большему числу критериев. Выбор лучшей библиотеки под мои задачи всегда превращался в задачу на много часов ресёрча (чтение рандомных статей на Medium про какую‑то одну библиотеку).

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

В этой статье я буду считать, что вы уже знаете, что такое MVI, и у вас есть опыт с архитектурой приложений на Kotlin. Это не гайд по реализации MVI с нуля, а сравнение уже существующих решений.

Итак, без лишних вступлений, давайте перечислим топ‑4 архитектурных фреймворка в 2025-2026 году и их плюсы и минусы.

Лучшие Kotlin‑библиотеки MVI / стейтменеджмента (2025 - 2026):


MVIKotlin

Это, вероятно, самая “уважаемая” архитектурная библиотека в экосистеме сейчас. Она существует уже очень давно, у неё много демо‑приложений и небольшая, но цельная экосистема (навигационный фреймворк Decompose и мультиплатформенная утилитная библиотека Essenty).

MVIKotlin известна своим простым, “без лишней воды”, жёстко зафиксированным дизайном, который поощряет разделение ответственности и следование Redux.

Как реализовать MVI‑архитектуру с MVIKotlin

Для каждой новой фичи или экрана минимум нужно создать такие сущности:

И опционально (для кастомного старта и создания) вам понадобятся:

Библиотека обязывает использовать Message - ещё один слой индирекции поверх MVI, типичный для архитектуры Elm/TEA. Причины его существования:

У вас будет отдельная, хорошо тестируемая функция/объект Reducer:

 val lceReducer = Reducer<LceState, LceMessage> { msg ->
      when (msg) {
          is LceMessage.Loading -> LceState.Loading
          is LceMessage.Success -> LceState.Content(msg.items)
          is LceMessage.Failure -> LceState.Error(msg.throwable.message)
      }
  }

И затем Executor, который диспатчит Message, Label и Action:

class LceExecutor(
    private val repo: LceRepository,
    mainContext: CoroutineContext = Dispatchers.Main
) : CoroutineExecutor<LceIntent, LceAction, LceState, LceMessage, LceLabel>(mainContext) {

    override fun executeAction(action: LceAction) { load() }         // bootstrap-путь
    override fun executeIntent(intent: LceIntent) { load() }         // путь Refresh/Retry

    private fun load() {
        dispatch(LceMessage.Loading)
        scope.launch {
            runCatching { repo.load() }
                .onSuccess { dispatch(LceMessage.Success(it)) }
                .onFailure {
                    dispatch(LceMessage.Failure(it))
                    publish(LceLabel.ShowError(it.message))
                }
        }
    }
}

Потом можно создать Store:

fun createLceStore(
    repo: LceRepository,
    storeFactory: StoreFactory = DefaultStoreFactory(), // или обернуть LoggingStoreFactory/TimeTravelStoreFactory
    autoInit: Boolean = true
): Store<LceIntent, LceState, LceLabel> = storeFactory.create(
    name = "LceStore",
    initialState = LceState.Loading,
    bootstrapper = SimpleBootstrapper(LceAction.Bootstrap), // пробрасываем Action при старте
    executorFactory = { LceExecutor(repo) },
    reducer = lceReducer,
    autoInit = autoInit
)

Как видите, это довольно многословно, но при этом просто для понимания и опирается на знакомые концепции: фабрики, bootstrapper, executor и store.

Image

Плюсы / преимущества MVIKotlin

Главные плюсы MVIKotlin - она навязывает конкретную структуру кода и намного ближе к классическому Redux‑паттерну, чем другие библиотеки.

Она не использует никаких платформенных/сторонних зависимостей и не привязывает вашу логику к UI, делая бизнес‑логику более универсальной и оторванной от особенностей платформы или конкретного фреймворка.

MVIKotlin - единственный популярный MVI‑фреймворк, который не зависит от Kotlin coroutines и позволяет подключить любой свой реактивный стек: Reaktive, RxJava или Compose state. MVIKotlin проста и понятна, потому что каждый экран и фича следуют одним и тем же правилам. Она почти не оставляет пространства для творческого подхода и вольностей в реализации фич - и это может быть как плюсом, так и минусом, в зависимости от ваших задач.

Внутрянка библиотеки проста для понимания, её легко повторить и с ней легко работать. Там нет “чёрной магии” или странного поведения, которого вы бы не ожидали. Всё хорошо задокументировано. MVIKotlin много лет поддерживается и остаётся стабильной, так что маловероятно, что её забросят или внезапно кардинально поменяют. Плюс - отличное покрытие тестами в самой библиотеке и в целом высокая тестируемость любого кода, который вы пишете с её помощью (по дизайну).

У MVIKotlin есть зрелый и богатый по возможностям плагин time‑travel‑отладки, который довольно бесшовно интегрируется в ваш код. Так что вы получаете мощные возможности дебага и даже Chrome‑расширение с теми же фичами для веб‑приложений.

Библиотека даёт большой каталог разных примеров и демо‑приложений с интеграцией с разными DI‑фреймворками, навигационными библиотеками, UI‑фреймворками и даже языками (Swift). Я нашёл заметное число OSS‑проектов, которые её используют.

Минусы и недостатки MVIKotlin

MVIKotlin реализует не только паттерн MVI, но и надстраивается над ним, вводя дополнительный слой индирекции в виде Message. Делается это в основном ради тестируемости, но это не единственный способ сделать reducer хорошо тестируемым. Это добавляет заметное количество бойлерплейта и многословности.

Библиотека вводит дополнительные классы, интерфейсы и сущности, которые формально не “нужны”, например:

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

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

По моему анализу 100+ фич, она покрывает заметно меньшую часть того, что дают другие фреймворки (всего 29 фич против 76 у топовой библиотеки из ~100). У неё нет сохранения состояния, интерцепторов, декораторов, DSL‑синтаксиса, управления подписчиками, coroutine‑first интеграции и ещё кучи дополнительных штук.

Философия и простота библиотеки накладывают свои требования и на работу с потоками. Библиотеку предполагается использовать только на главном потоке, с очень немногими местами, где выполнение можно вынести с main‑треда. Даже там для некоторых операций, например производства состояния, требуется явное переключение контекста. Библиотека не предлагает средств потокобезопасности и не даёт встроенных возможностей для параллельных задач, долгоиграющих задач, фоновой работы, участия в общем event bus, не реализует chain of responsibility или какие‑то другие паттерны из коробки. Всё это нужно строить поверх библиотеки своими силами, и часто - из‑за намеренных ограничений и строгого дизайна - это будет либо тяжело, либо просто неуместно.

Для кого MVIKotlin?

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

MVIKotlin подойдёт, если вы хотите максимальную архитектурную свободу в проектировании собственных слоёв и надстроек или если вам нужна библиотека, нейтральная по части реактивной реализации. Плюс - у библиотеки зрелые и стабильные инструменты отладки с богатой функциональностью и плагином для IDE.

Если у вас большая команда или зрелое приложение и вы хотите, чтобы решение легко понимали разные разработчики и оно уже было широко принято в сообществе, то есть большая вероятность, что если вы наймёте разработчика, знакомого с MVI, он уже будет знать и MVIKotlin. Это экономит время, упрощает найм и снижает вариативность подходов к изменениям и особенно к добавлению нового кода - следовательно, вам проще придерживаться общих стандартов, а это меньше багов и проблем в больших командах.


FlowMVI

Если коротко, FlowMVI - полная противоположность MVIKotlin. FlowMVI жёстко уходит в сторону свободы. Её основная философия в том, что архитектурная библиотека не должна вас ограничивать, а должна расширять ваши возможности.

Библиотека делает ставку на свою плагинную систему - нечто среднее между паттерном chain of responsibility, декоратором и интерцептором. Плагины пронизывают каждый слой библиотеки, и это одновременно плюс и минус.

Как использовать Kotlin FlowMVI

Минимальный объём кода, который нужно написать:

Всё. Остальное (state, intents и т.п.) опционально.

Но, скорее всего, для каждой фичи вы будете описывать:

На этом всё. Вы, возможно, уже заметили, что здесь всё опционально. То есть набор ограничений, которые кладёт на вас библиотека, практически нулевой - от вас требуется только один объект, чтобы получить все возможности библиотеки. Значит, вы можете делать что хотите, сразу.

Логика фичи будет выглядеть примерно так (эквивалент примера с MVIKotlin):

private typealias Ctx = PipelineContext<LCEState, LCEIntent, LCEAction>

class LCEContainer(
    private val repo: LCERepository,
) {

    val store = store(LCEState.Loading) {

        recover { e ->
            updateState { LCEState.Error(e) }
            action(LCEAction.ShowError(e.message))
            null
        }

        init { load() }

        reduce { intent ->
            when (intent) {
                is ClickedRefresh -> updateState {
                    launch { load() }
                    LCEState.Loading
                }
            }
        }
    }

    private fun Ctx.load() = updateState {
        LCEState.Content(repo.load())
    }
}

Как видите, это намного более лаконично, уже даёт дополнительные фичи из коробки и читается почти как английский текст, но при этом под капотом много “чёрной магии” с PipelineContext, корутинами и кучей лямбд.

FlowMVI diagram

Плюсы и преимущества FlowMVI

FlowMVI - это просто монстр, который из коробки даёт огромное количество возможностей: 76 разных фич и улучшений, которые стараются покрыть как можно больше потребностей.

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

Библиотека отлично показывает себя по ключевым параметрам, которые я учитывал. Она:

Хотя UI‑тестов и end‑to‑end‑тестов в самой библиотеке сейчас нет, по моим наблюдениям end‑to‑end‑тестирование вообще довольно редко встречается именно в архитектурных библиотеках.

Корутины уже и в Kotlin, и в Compose - сущности первого класса, и FlowMVI намеренно построена на корутинах. Это зависит от вашего стека, но если вы используете корутины, вы будете рады узнать, что почти весь API FlowMVI - suspend‑функции, и многие операции можно выполнять безопасно, опираясь на structured concurrency. Библиотека не заставляет вас использовать disposable‑ы, слушатели, callback‑и и т.п., перекладывая это на корутины.

Библиотека сделана с учётом параллельных задач и параллелизма как сущностей первого класса. Основная философия - писать асинхронные и реактивные приложения очень быстро. Она даёт вам возможность запускать логику параллельно с хорошей потокобезопасностью, защитой от data race из коробки и при этом сохраняет один из лучших уровней производительности и в однопоточном сценарии. Это реально уникально среди архитектурных библиотек, потому что очень немногие фреймворки из моего списка вообще поощряют вас писать параллельный и реактивный код - в отличие, например, от MVIKotlin, которая намеренно загоняет всё в main‑тред, или Orbit MVI, которая заявляет поддержку фонового выполнения и параллельных задач, но не даёт хелперов, утилит или синхронизации, чтобы ваш мультипоточный код был безопасен.

Это единственная библиотека, которую я видел, которая позволяет вам:

Если вы хотите ввести какие‑то ограничения для своего кода - например, вы тестируете reducer и хотите, чтобы он был чистым, - это можно легко сделать через библиотеку, написав свой плагин. То есть если вы хотите ограничить/обработать API‑поверхность библиотеки под свои нужды, это довольно просто: библиотеке от вас нужен только один объект Store, а не пачка интерфейсов, фабрик и обёрток.

Хотя первое впечатление может быть иным, библиотека не требует использовать её именно на уровне UI‑архитектуры или с каким‑то конкретным фреймворком. Вы можете применять её с разными UI‑библиотеками или даже вне UI‑кода. При всей насыщенности фичами библиотека почти не навязывает структуру кода. Она не требует особого устройства слоёв, не заставляет вас иметь сайд‑эффекты или обрабатывать ошибки строго определённым образом и даже даёт несколько путей через соседние библиотеки типа ApiResult. FlowMVI не “воняет Android‑ом” и не выглядит замороченной.

Минусы и проблемы FlowMVI

Наверное, самая большая проблема FlowMVI в том, что она может показаться сплошной чёрной магией. Когда вы впервые открываете библиотеку, она утверждает, что вы сможете начать пользоваться ей за 10 минут. Но чтобы по‑настоящему понять, что происходит под капотом, все особенности API, как они сочетаются с корутинами, structured concurrency и друг с другом, нужно серьёзно зарыться в код и прочитать немало документации - намного больше, чем в случае MVIKotlin или Orbit MVI.

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

Гибкость библиотеки - задуманный плюс, но он же и минус. В официальной документации прямо сказано, что вы можете написать одно‑единственное расширение как “плагин”, и в зависимости от того, где вы поставите его в файле (на какой строке кода вы “установите” этот плагин), может полностью поменяться поведение логики - изменится то, когда и как обрабатываются интенты, начнут проглатываться исключения, или исчезнет логирование и вызовы репозиториев. Я бы не дал джунам бегать с такой гаубицей.

Кроме того, если вы не используете корутины, пользоваться библиотекой почти не получится, потому что она целиком построена на корутинах. Мало того, что нужно уметь с ними работать - ещё и нет адаптеров для RxJava или Reaktive, как в MVIKotlin. То есть тут либо корутины, либо ничего. Лично я не считаю это минусом, так как корутины - нативная часть Kotlin, но да, эта библиотека ещё сильнее, чем Compose, жёстко привязывает вас к ним.

Если придираться, time‑travel плагин в FlowMVI мне показался слабее, чем у MVIKotlin.

И наконец, нельзя не отметить, что библиотека намного свежее остальных, так что поиск по GitHub дал очень мало примеров использования, демо и интеграций. Скорее всего, вам будет сложнее найти подходящие примеры, готовые реализации и наработанные best practices, чем с другими библиотеками в списке. Готовьтесь экспериментировать, исследовать и описывать свой собственный стиль использования этой библиотеки.

Сценарии использования FlowMVI

Я думаю, FlowMVI отлично подходит для:

Для таких команд FlowMVI будет лучше любого другого варианта. Просто за счёт огромного числа фич вы сможете писать код на FlowMVI невероятно быстро, не думать о множестве проблем (краши, аналитика, потокобезопасность, data race, дебаг, сбор логов) и опираться на библиотеку на каждом шаге. Какое бы требование ни пришло из продукта, почти наверняка в FlowMVI найдётся что‑то, что вам поможет. И даже если не найдётся, библиотека устроена так, что несколькими строчками можно расширить бизнес‑логику в любой части приложения или даже сразу во всех, без серьёзных рефакторингов или переделки кодовой базы.

Если у вас уже есть зрелый продукт с большой командой, вы нанимаете разработчиков, которые не знакомы с FlowMVI (т.к. она новая), и вы не готовы вкладываться во внутреннее обучение, то лучше обходить FlowMVI стороной. Каждый разработчик, которого вы будете онбордить на FlowMVI, должен прочитать документацию, залезть во внутренний код, иметь чёткие примеры, как её использовать, понимать её устройство хотя бы частично, хорошо разбираться в корутинах и быть достаточно сильным инженером, потому что FlowMVI собрала в себе очень много архитектурных паттернов, которые надо действительно понимать, чтобы использовать эффективно.

И, наконец, если вы застряли на RxJava или у вас Java‑проект, то тут вам не повезло, потому что FlowMVI практически невозможно использовать с RxJava и Java в целом.


Orbit MVI

Это, наверное, самый известный и популярный Kotlin‑фреймворк MVI на сегодня и в представлении не нуждается.

Как использовать Orbit MVI в 2026/2025

Для любой фичи, которую вы делаете, вам нужно создать:

class LceViewModel(
    private val repo: LceRepository,
) : ViewModel(), ContainerHost<LceState, LceSideEffect> {

    override val container = container<LceState, LceSideEffect>(LceState.Loading) {
        load() // bootstrap; запускается как неявный intent 
    }

    fun load() = intent {
        reduce { LceState.Loading }
        runCatching { repo.load() }
            .onSuccess { items -> reduce { LceState.Content(items) } }
            .onFailure { e ->
                reduce { LceState.Error(e.message) }
                postSideEffect(LceSideEffect.ShowError(e.message))
            }
    }
}

Как видите, код с этой библиотекой очень лаконичен, я бы сказал - даже лаконичнее, чем с FlowMVI.

Преимущества и плюсы Orbit MVI

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

Она специально опирается на ментальную модель “MVVM с плюшками”, и действительно, в коде мы видим знакомые сущности: вьюмодель и её методы. Intents - это простые лямбды, содержащие блоки кода, а не какие‑то мудрёные иерархии из модель‑ориентированных реализаций MVI. Хотя FlowMVI тоже поддерживает MVVM+‑стиль, Orbit MVI ближе к привычным нашему опыту вещам вроде вьюмоделей и структурирует код проще.

Orbit - зрелый фреймворк, и на него часто ссылаются. Я нашёл больше 130 open source‑проектов, использующих Orbit, так что любому, кто хочет понять, как с ним работать, будет довольно легко. Он используется в проде минимум с 2019 года, так что фреймворк стабилен, и вам не стоит ждать сильных изменений. Существует немало статей о том, как пользоваться Orbit MVI и как его интегрировать, так что я не вижу смысла расписывать гайды здесь слишком подробно.

Библиотека также поддерживает Kotlin Multiplatform. Хотя в доках я вижу сильный уклон в сторону Android, это больше наследие, чем реальный приоритет, и в целом всё неплохо работает в мультиплатформе, особенно с учётом того, что библиотека почти не лезет в ваш остальной код, например UI‑связанный (в отличие от MVIKotlin, которая подталкивает к Decompose, или FlowMVI, который всё прячет за магическим DSL).

Orbit MVI - вероятно, единственный популярный фреймворк, который нативно поддерживает UI‑тестирование на Android, так что это большой плюс, если вы серьёзно опираетесь на UI‑тесты и интеграционные тесты именно на Android.

Минусы и проблемы Orbit MVI

Несмотря на популярность и широкое распространение, Orbit MVI сейчас не очень активно развивается. Было неожиданно увидеть документацию, которая до сих пор сильно завязана на Android и в целом довольно скромна. На самом деле набор фич и возможностей Orbit MVI заметно шире, чем заявлено в доках. Это меня удивило, потому что по моему анализу библиотека набирает очень приличный балл и по функциональности примерно на уровне MVIKotlin, хотя и с другим уклоном.

И FlowMVI, и Orbit разделяют похожую философию: они стараются быть лёгкими, “фичастыми” библиотеками, которые не лезут вам под руку. Но сейчас FlowMVI даёт намного больше и в более удобной упаковке. Похоже, Orbit MVI до сих пор тащит за собой некий Android‑багаж, который тормозит появление новых интересных фич. Или это просто не в фокусе авторов.

Так или иначе, рассчитывать на паритет по фичам с FlowMVI точно не стоит - разрыв огромный. Если смотреть только на набор возможностей и удобство использования, в 2026 году тяжело советовать Orbit MVI вместо FlowMVI.

В отличие от MVIKotlin и FlowMVI, Orbit не имеет удалённого дебаггера или плагина к IDE, так что если для вас важна отладка и инструменты разработчика, это может быть критичным минусом.

Когда стоит использовать Orbit MVI

Я бы сказал, самый железный аргумент в пользу Orbit MVI - если ваша команда уже привыкла к MVVM, особенно если у вас сейчас MVVM‑ или MVVM+‑приложение с самописной реализацией, и вы хотите переехать на поддерживаемый архитектурный фреймворк вместо in‑house кода. Тогда вы почти наверняка выберете именно Orbit MVI - просто потому, что он ощущается “домашним” и позволит переехать на MVI максимально плавно и постепенно.

Миграция на Orbit MVI, скорее всего, будет самой лёгкой:

Если вы стартуете новый проект, я бы советовал Orbit, только если у вас команда разработчиков, хорошо знакомая с MVVM, и вы хотите максимально быстро включиться в работу и начать клепать фичи. В остальных случаях я бы рассматривал либо FlowMVI, либо MVIKotlin как более дальновидный вариант.


Ballast

Ballast - довольно малоизвестный архитектурный фреймворк, и я сам не до конца понимаю почему, потому что это отличный претендент и сильная архитектурная библиотека на 2026 год и дальше. У неё простой, но достаточно жёсткий API без лишних украшений, при этом она остаётся довольно гибкой и показывает впечатляющий список фич - вторая по силе после FlowMVI, если смотреть на количество функциональности.

Как использовать Kotlin‑библиотеку Ballast

Чтобы реализовать LCE‑пример из начала статьи, вам нужно создать:

Дальше логика:

class LceInputHandler(
    private val repo: LceRepository,
) : InputHandler<LceInput, LceEvent, LceState> {

    override suspend fun InputHandlerScope<LceInput, LceEvent, LceState>.handleInput(input: LceInput) = when (input) {
        is LceInput.Load -> {
            updateState { it.copy(isLoading = true, error = null) }
            sideJob("load") {
                runCatching { repo.load() }
                    .onSuccess { postInput(LceInput.Loaded(it)) }
                    .onFailure { postInput(LceInput.Failed(it)) }
            }
        }
        is LceInput.Loaded -> updateState { it.copy(isLoading = false, items = input.items, error = null) }
        is LceInput.Failed -> {
            updateState { it.copy(isLoading = false, error = input.error.message ?: "Unknown error") }
            postEvent(LceEvent.ShowError(input.error.message ?: "Unknown error"))
        }
    }
}

object LceEventHandler : EventHandler<LceInput, LceEvent, LceState> {
    // В Compose собираем события и показываем snackbar/навигацию внутри LaunchedEffect (через интеграции под платформу)
    override suspend fun EventHandlerScope<LceInput, LceEvent, LceState>.handleEvent(event: LceEvent) = TODO()
}

fun createLceViewModel(
    repo: LceRepository,
    scope: CoroutineScope,
): BallastViewModel<LceInput, LceEvent, LceState> = BasicViewModel(
    config = BallastViewModelConfiguration.Builder()
        .withViewModel(
            initialState = LceState(isLoading = true),
            inputHandler = LceInputHandler(repo),
            name = "LceViewModel",
        )
        .build(),
    eventHandler = LceEventHandler,
    coroutineScope = scope,
).also { it.trySend(LceInput.Load) }

Как видите, библиотека не шутит, когда говорит, что она opinionated. Структура довольно своеобразная, но посмотрим, что это нам даёт.

Преимущества Ballast

Я бы сказал, эта библиотека не пытается понравиться всем. Она не старается быть максимально “крутой” и модной, как FlowMVI, не ориентируется на “дружелюбие к джунам”, как Orbit, и не хвастается “жёсткой структурой”, как MVIKotlin. Вам просто будет кайфово с Ballast, если вы “ловите её волну”, и всё.

Ballast - вторая библиотека из 70, которые я сравнивал, по количеству фич и функциональности. Она даёт готовое решение с классными улучшениями и фишками для каждого слоя архитектуры вашего приложения. Она приносит фичи и трюки для:

Ballast имеет нативную интеграцию с Firebase Analytics и может стыковаться с любыми другими системами аналитики. В ней есть богатая концепция интерцепторов и декораторов, в чем‑то похожая на реализацию в FlowMVI, но при этом остаётся довольно простой и верной принципам MVI.

Мне нравится Ballast, потому что она вдохновляет. Она даёт много фич, но при этом остаётся по‑деловому лаконичной. Она не пытается так же сильно, как FlowMVI, “быть особенной”.

У Ballast есть уникальная фича: она позволяет синхронизировать ваше состояние и intent с реальным удалённым сервером - ни одна другая библиотека этого не делает. И это ещё не считая других интересных вещей, которые вы найдёте в доках. Определённо стоит посмотреть и вдохновиться, что вообще можно запилить.

Инструменты разработчика у Ballast тоже хороши: есть свой плагин для time travel и отладки с немалым набором применений и богатая экосистема, заточенная под то, чтобы делать приложения быстро и удобно (кэширование, ретраи и т.д.). Я бы сказал, это и есть ключевая философия Ballast - это такой почти “полный комплект” для того, чтобы прямо на ней строить приложения. Вся идея выглядит довольно круто.

Недостатки Ballast

Как это часто бывает, та же opinionated‑природа Ballast - и главный минус. Ощущение такое, что библиотека пытается решить всё, но только одним конкретным способом. А что если вы не на одной волне с авторами Ballast? Тогда вам будет тяжело, очевидно.

Например, я заметил, что интеграция с Firebase Analytics шлёт события только определённым образом и не очень гибко настраивается под особые кейсы и страницы, в отличие от более общей, но гибкой реализации в FlowMVI. То же самое можно сказать про репозитории и кэширование.

Если ваши кейсы кэширования и структура репозиториев совпадают с тем, как это предлагает Ballast (после того, как вы осилите увесистый кусок документации на официальном сайте), у вас всё будет супер. Но как только появится кейс, не вписывающийся в схему, или понадобится изменить подход, вам, возможно, придётся выкинуть фишки Ballast и писать своё. Это ведёт к фрагментации и банальному переписыванию похожего кода с маленькими различиями. Может оказаться, что набор кейсов, о которых подумали авторы, для вас банально не закрывает ситуацию, а гибкости фич не хватает. И это уже реальная проблема.

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

Третий момент - библиотека местами ощущается как смесь разных стилей. FlowMVI очень последователен по стилю - даёт такой “вайб зумеров: давайте делать всё лямбдами”. А Ballast использует смесь java‑стайл билдера, DSL, лямбд и фабрик. У неё есть то, что похоже на reducer, но формально это не reducer - это InputHandler. Есть вьюмодели и концепция вьюмоделей, но сами вьюмодели по сути - просто обёртки вокруг билдера вьюмоделей. Я считаю, терминология местами запутывает. Хотя концептуально всё логично, библиотеке бы не повредила чуть большая цельность стиля.

Сценарии использования Ballast

Ballast очень зайдёт вам, если ваши кейсы похожи на кейсы авторов библиотеки.

Больше всего выиграют маленькие команды, которые делают определённый класс приложений и не боятся принимать чужие архитектурные подходы. В этом случае, если ваши задачи совпадают с тем, на что ориентируется Ballast, вы сильно сэкономите время, уменьшите объём кода и получите очень крутой набор фич из коробки.

Если же вы не хотите следовать паттернам, которые предлагает Ballast, и у вас, например, огромный продукт, большой enterprise‑проект или, наоборот, очень простое приложение, и вы не хотите вникать во всё, что туда заложено, - вы не лучшая аудитория для Ballast.

Так что Ballast находится где‑то посередине, не уводя вас ни в одну сторону до конца.


Бонус: таблица‑сравнение 70 архитектурных библиотек

Признаюсь, я увлёкся, когда разбирал все эти библиотеки. Я нашёл больше 70 разных библиотек стейтменеджмента / MVI / архитектурных решений и сравнил их по 100+ критериям. Так что эта статья основана в основном на этом большом ресёрче.

Хочу поблагодарить коллегу Артёма за то, что он сделал первую часть исследования для нашего доклада на конференции Mobius. Недавно я решил обновить эти результаты: добавить больше критериев, фич и переоценить каждую библиотеку, потому что многие успели выпустить крупные релизы.

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

В таблице есть все достойные упоминания - обязательно посмотрите! Простите, если я не включил именно вашу библиотеку сюда - и так статья получилась огромной!

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

Сравнение 70 Kotlin‑архитектурных библиотек по 100+ критериям