Как стать автором
Обновить

Комментарии 17

Еще бы Dagger прикрутить для инжекции кода.
Код становится более тестируемым. В действительности, нам даже не нужны Instrumentation-тесты, т.к. достаточно протестировать презентер и убедиться, что после каждой операции наш LoginState имеет правильный набор данных


У вас в примере очень необычный тест: вы вызываете login(), а потом сами же в тесте processLoginResult.
Т.е. если вы забудете реализовать функцию login — то тест все равно будет проходить. Это нормально?

Поэтому у меня вопрос — так а как именно дождаться в тесте когда ваш стэйт обновится (если это обновление асинхронное)?
Согласен, выглядит необычно. Основное намерение этих тестов – проверить, что View (чем бы она ни была) получает правильный state при разных событиях. Другими словами, если логин прошел успешно – state.showSuccessLogin() должен быть true, если результат еще не пришел, state.showProgress() должен быть true, а state.isLoginActionEnabled() – false, и т.д. Вручную вызывается processLoginResult с нужным результатом для того, что бы исключить реальную операцию логина, какой бы она ни была. По хорошему, нужно вынести операцию логина в отдельную сущность и в тестах предоставлять mock-логин (думаю, так и сделаем), просто не хотелось отвлекать от идеи :)

Т.е. если вы забудете реализовать функцию login — то тест все равно будет проходить. Это нормально?

Как раз stateChecks свалится, потому что state не перейдет в ожидаемое состояние.
Протестировал приложение-пример, возник вопрос.

В примере с таймером написано: «The timer starts when the presenter is created and stops only when the presenter is destroyed. You can minimize, rotate, open another screen, but the timer will still work. The presenter will be destroyed only when the activity is finished (for instance, by pressing the back button).»

Провожу такой тест:
1. Запускаю экран с таймером, дожидаюсь, пока дотикает до 10.
2. Выхожу на домашний экран.
3. Симулирую «убиение процесса» командой: «adb shell am kill example.reamp».
4. Возвращаюсь в приложение, таймер при этом сбрасывается на 0.

Так и задумано, или таймер после убийства не должен сбрасываться?
Там два примера с таймером: Life Cycle 1 и Life Cycle 2. Кажется Вы смотрите на первый. Второй должен вести себя так, как Вы описали.
P.S. на самом деле первый пример тоже сохраняет значение таймера, просто он специально сбрасывается при старте
Спасибо за пояснение, значение таймера в Life Cycle 2 сохраняется.

Можно ещё это почитать, не то чтобы готовая библиотека, но один из подходов асинхронного подхода, который позволяет забить на повороты экрана:
https://m.habrahabr.ru/post/328512/

Спасибо, полезно!

Попробовал Вашу либу. Когда начал её расширять под свои нужды, нашёл проблему, из-за чего убрал с gradle зависимостей. Короче presenter жёстко привязан к ReampView. Я хотел сделать свой метод «onError(@StringRes messageId: Int)», и хотел в BasePresenter сделать подобный метод для удобства использования, но это оказалось невозможно:
fun onError(@StringRes messageId: Int) {
view.onError(messageId)
}

Буду ждать, когда reamp будет чуть более гибкий, потому что задумка с комбинированием MVP и MVVM мне понравилась, а писать свои костыли не сильно хочется :)
Возник вопрос. А что если мне нужно запустить какую-то анимацию только если данные изменились, не хранить же две копии состояния? И вообще есть ли какой-то способ на прямую влиять на view, а не скармливать в неё состояние целиком?
Поздравляю, вы изобрели Moxy :) habrahabr.ru/post/276189
Только она ещё и ViewState генерирует за вас.
Попробуйте, вам понравится.
В ней не реализовано разве что сохранение ViewState в Bundle. Но это вполне логично, т.к. вьюха должна отображать то состояние, о котором ей сообщил презентер. Если у вас после убийства системой ViewState восстановится, он не будет соответствовать текущему состоянию презентера. То если, появляется необходимость ещё следить обязательно за ним и сохранять в бандл если необходимо.
Если эта фича (сохранение в бандл) включена по умолчанию — это опасный подводный камень, т.к. допускает рассинхронизацию вьюхи и презентера после восстановления приложения.
Спасибо за поздравление, конечно, но мы изобрели это еще в конце 2015-го, если включить режим занудства :)
Концептуально, это разные вещи. В Moxy это хоть и называется ViewState, но по сути представляет собой список вызовов методов view с параметрами и какой-нибудь стратегией, и нужна, судя по всему, лишь для отложенного вызова методов активити/фрагмента, когда они будут доступны. В эту ViewState нельзя просто заглянуть и посмотреть на контент. В Reamp используется в прямом смысле ViewModel, её можно проверить, передать в DataBinding, сохранить куда угодно и много чего еще. У каждого подхода есть плюсы и минусы.

Попробуйте, вам понравится.

Не хочется, что б это выглядело как бросание помидороами, но с прагматичной точки зрения, не очень нравится. Выглядит так, что Moxy удобный до тех пор, пока используешь его по протоптаной дорожке. Пара примеров, которые сразу можно найти:
  • Выглядит так, что в Moxy нельзя сделать CustomView, не используя moxy-родителя активити или фрагмент. CustomView требует mParentDelegate, а откуда его взять?
  • В Moxy странный подход к менеджменту презентеров. Если использовать Moxy-фрагменты внутри ViewPager+FragmentStatePagerAdapter, то при пролистывании фрагментов их презентеры никогда не будут уничтожены, даже если закрыть activity
  • Исходники MvpAppCompatActivity и MvpAppCompatFragment очень разные. Логика связывания фрагмента/активити с презентером отличается (не говоря уже про обычную View). Это значит, что в том случае, когда я использую свою реализацию базовой активити, мне нужно постоянно при обновлении либы сверяться с кодом из фреймворка. Это сложно поддерживать и дебажить.


допускает рассинхронизацию вьюхи и презентера после восстановления приложения.

Презентер получит восстановленное состояние раньше View, у него как раз есть все возможности, что бы понять, что у нас уже есть и что еще нужно сделать (если нужно). Это, пожалуй, одно из основных отличий от Moxy, в котором что бы восстанавливаться после перезапуска все равно придется писать какой-то код по сохранению/восстановлению состояния.
Я бы не сказал, что у Moxy много примеров. Даже тот который у них висит с багом. Список нормально не работает, слетает позиция. И исправлять явно не собираются. А Телеграм у них просто как фан клуб. Реального совета по библиотеке не дождешься. В целом крайне разочарован, потому что идея у Moxy хорошая.
Moxy хороший инструмент. Разобраться в том как она работает — дело нескольких часов. Она решает одну проблему (жизненный цикл) и делает это хорошо. Что ещё нужно?)
Идея хорошая. А инструмент не очень. И что самое ужасное без хорошей документации и не рабочими примерами. Мне не понравилось, личное мнение.

Не заметил сильных преимуществ перед Architecture Components. Да, Reamp умеет автоматом сериализовать и восстанавливать данные. Но это такой себе плюс, так как используется Serializable, что не лучшим образом сказывается на производительности, если речь идет об Android. Поэтому я бы такой подход не использовал. А в остальном — ViewModel из Architecture Components так же отвязана от жизненного цикла и переживает повороты, LiveData отлично возвращает последнее состояние при подписке на нее. И да, у меня они тоже работают с Kotlin и Data Binding (с небольшим количеством костылей, но на их изобретение я потратил однажды минут 15, и дальше проблем с этим не наблюдаю).

В отличие, к примеру, от новых Android Architecture Components, нам не требуется целого зоопарка вспомогательных технических классов и аннотаций, чтобы решить те же проблемы.


Вообще достаточно будет двух классов: ViewModel и LiveData, чтобы почти дословно повторить ваш пример. И при этом не придется наследовать свои Activity от непонятно чего.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий