Pull to refresh
13
0
Артур @forceLain

Пользователь

Send message
Переключение настроек полностью убивает процесс приложение и создает новый, по этому все презентеры тоже убиваются.
onSaveInstanceState()/onCreate() действительно вызываются, но не те, к которым мы привыкли, а новые, те что с 21 API: onSaveInstanceState(android.os.Bundle, android.os.PersistableBundle) и onCreate(android.os.Bundle, android.os.PersistableBundle)
Ага, а потом напишем в нем onCreateView(), и получим фрагмент :)
Все эти фрагменты, которые наполовину View, наполовину Activity появились не просто так. При переиспользовании одних и тех же View в разных activity приходилось постоянно дублировать прокидывание разных колбэков жизненного цикла вьюшкам. Посмотрите на класс Webview, который появился гораздо раньше фрагментов и увидите у него webview.onPause(), webview.onResume().
Представьте, что вы написали свой видеоплеер MyVideoPlayer и добавили его в Activity1. Теперь у вас появилось требование останавливать видео, если экран с видео перекрывается другим экраном. в Activity1.onStop() я напишу myVideoPlayer.stop(). Потом у меня появляется Activity2 с таким же плеером и таким же требованием. Опять переопределять onStop()? Конечно нет. Создавать общую ViedeoPlayerActivity? Ну можно, если Activity1 и Activity2 это позволяют (все помнят, что в java нет множественного наследования). Я могу создать VideoFragment и это решит все мои проблемы. Как это будут решать ребята из squareup?
К картинке-графу жизненного цикла фрагмента тоже большая претензия, т.к. там намешано всё, кони, люди… Уже не раз обсуждалось, что onSaveInstanceState/onResoreInstanceState это не методы жизненного цикла, они могут быть вызваны совершенно в разных ситуациях и в разных местах жизненного цикла. Всё что от вас требуется — это сохранять состояния и возвращать состояние в них. У View между прочим тоже есть onSaveInstanceState() и onResoreInstanceState(), почему их нет на том графе, что Вы нашли, непонятно.
onPrepareOptionsMenu, onCreateOptionsMenu это вообще шаблонные методы и непонятно, почему эти 2 метода попали в этот граф, а десяток других шаблонных методов не попали.

Еще навскидку, в графе лайфцикла View не преведены onFinishTemporaryDetach(), onStartTemporaryDetach(), onVisibilityChanged(), onFinishInflate(), вероятно еще что-то
Перейти с ListView на RecyclerView будет в любом случае непросто, вне зависимости от того, используете вы фрагменты или нет

Смотрите, если я использую фрагмент, то при переходе к RecyclerView поменяется layout-файл фрагмента и внутри фрагмента поменяется всё, что касается ListView. У нас появится новый адаптер, новый RecyclerView и новый OnClick-колбэк. Но это всё будет внутри фрагмента. Все, кто использовал мой фрагмент вообще не заметят никакой разницы при переходе на RV.
В статье же, автор то ли схитрил, то ли поленился показать как заполняется MyListAdapter. Если MyListAdapter выходит за рамки ItemListView, то при переходе да RV придется заменить и его адаптер во всех контейнерах которые его используют. Если же данные в ItemListView попадают через его гипотетический метод ItemListView.showData(items), а оттуда уже в адаптер, то уже не все так плохо. Правда все равно остается вероятность, что кто-то не воспользуется вашим контрактным методом showData(), а установит в ItemListView свой адаптер.
Точно такая же история с OnItemClickListener. В статье автор взял и просто напрямую написал внутри своей ItemListView MainActivity activity = (MainActivity) getContext(). Это что, опять "пример, а не production-code"? По хорошему или родительский контейнер должен установить в ItemListView колбэк на клик (и тогда придется переписывать все контейнеры при миграции на RecyclerView.OnClickLisetener()) или ItemListView должен дергать у контейнера onItemClicked, который, разумеется должен быть скрыт за интерфейсом как это делается для фрагментов. Да, наличие презентеров никак эту проблему не решает, т.к. все колбэки просто переносятся из View в Presenter.
Я надеюсь, я не сумбурно объяснил свои опасения
Давайте не будем писать 100500 фрагментов, давайте лучше напишем 100500 кастомных лэйаутов.
На сколько легко будет передать ItemListView из примера, если потребуется перейти со старого ListView на новый RecyclerView?
getChildAt(0), getChildAt(1) это шедевр. Дотаточно по неосторожности добавить в xml разметку еще один элемент что бы код сломался.
onFinishInflate(), listViewAttached() — те же самые колбэки жизненного цикла, только уже у View. Попробуйте нарисовать самостоятельно такой же граф жизненного цикла для View, как приводится для фрагментов, проще он будет не намного.
Что если мой ListFragment использует разные лэйауты для sw-600dp и sw-700dp, но по коду ничем не отличается? Сделать классы ItemListView600 и ItemListView700?
При всем моем восхищении чуваками из squareup, спасибо, но нет.
У нас View только отражает состояние ViewState и передает клики и т.п. презентеру. ViewState изменяясь сообщает об этом View и View уже показывает прогрессы или пезультаты или еще чего. В onSaveInstanceState базоый презентер серелизует ViewState в бандл стандартным ObjectOutputStream и потом достает из бандла в onCreate() стандартным же ObjectInputStream. Этого хватает для большинства экранов. Если на каком то экране во ViewState требуется положить что-то такое, чего не стоит серелизовать этими средствами, то можно переопределить серелизацию/десерелизацию конкретно для этого экрана и пары ViewState-Presenter.
Интересно, что вы еще написали себе аннотации. Лично мне не очень хотелось писать собственные кодогенерирующие аннотации для инжекта, на крайний случай хватает инжектов из даггера, но раз у вас есть, наверно стоит взглянуть на них тоже.

А ваше решение где-нибудь опубликовано?


Ага, на внутрикомпанейском гитлабе :) Вероятно, когда нибудь оформим и в общий доступ
Да, если вдаваться в детали, мы положили ViewState в активити для того, что бы иметь возможность «вернуть все как было» не только при повороте экрана, но и при пересоздании всего процесса приложения (весьма частый кейс в Android 6 со своими новыми runtime пермишеннами). Конечно после таких издевательств над процессом в нем не останется никаких презентеров в статичном хранилище, а вот весь ActivityTask андроид нам любезно восстанавливает и отдает savedState, а там наша ViewState лежит себе :)
А для «легкого» пересоздания View, как в случае с поворотом, мы как раз так же используем хранилище презентеров. Вот только оно не статичное, а создается и лежит внутри Application. А тегирование для View мы используем не только для того, что бы автоматически переаттачить тот же презентер к новой View, но и для того, что бы разделять одинаковые Activity в одном AcivityTask и вешать им разные презентеры. Это на примерно такой случай: открыть активити «чатик с другом», из неё открыть активити «список друзей друга», а из неё открыть еще одну активити «чатик с еще одним другом». В итоге получим первую и последнюю активити одного класса, но чатик там должен быть разный, соответственно, и презентеры тоже разные.
Очень хороший подход! Очень забавно, что мы, создавая свою MVP либу, написали все до безобразия похоже. Я даже вижу проблемы, которые натолкнули вас на те или иные решения: тэгирование View, что бы иметь возможность переаттачить к ней презентер после поворота экрана, MVP-делегаты, что бы решить проблему отсутствия множественного наследования в Java и уже имеющиеся несопоставимые Fragment/DialogFragment, Activity/AppCompatActivity и т.п. (правда их мы подсмотрели в Nucleus), ViewState, что бы облегчить страдания при пересоздании View/Fragment/Activity из небытия, правда мы её назвали StateModel :)
Чем хочу поделиться из наших решений:

1) ViewState умеет сама записывать себя в Bundle savedState по умолчанию используя стандартный механизм серелизации java, но есть возможность повлиять на это переопределив в нужный метод в своей конкретной ViewState.

2) для связи между View -> Presenter -> ViewState -> View мы используем RxJava и её PublishSubject и BehaviorSubject, из которых легко строится очередь из событий и ожидание на их публикацию. Во-первых, это помогает вьюшке, подписавшись на изменения из View получить правильное состояние даже если ответ от сервера пришел как раз в тот момент, когда активити еще была в процессе пересоздания из-за поворота, например. Во-вторых, коммуникации между View, Presenter и ViewState защищены от эксепшенов в том смысле, что если что-то случится — мы то 100% вероятностью получим это в onError, даже если забыли поставить проверну на NPE. Ну и в-третьих, все подписки View на ViewState отписываются через делегат и по этому нигде ничего не течет :)
Для того, что бы периодически опрашивать сервер не нужно городить будильники из AlarmManager. Нужен SyncAdapter. Но в вашем случае, как мне кажется, вместо постоянного периодического опроса сервера лучше подойдет сервис и long polling в нём.
Необходимо в пределах 30-60 секунд оповестить пользователя о некотором действии

В конечном счете было принято решение о том, чтобы держать свое соединение с сервером в связке с GCM.

Надо было сразу с этого начинать. И убрать GCM.
Нам это уведомление не нужно, поэтому мы воспользовались следующим велосипедом… запустить одновременно с первым сервисом второй… затем убить второй сервис.

Жесть конечно. А потом все плюются от качества ПО в маркете…
в области тяжёлых и мощных процессоров

Советские микрокалькуляторы — самые крупные микрокалькуляторы в мире! (с)
У вас все операции работают внутри ChronosService, который на самом деле не сервис в терминах андроида, а просто сингтон, который содержит ExecutorService, стало быть если в процессе приложения не останется ни одной активити/сервиса, он (процесс) будет первоочередным кандидатом на удаление при чистке ресурсов системой и не важно, что ExecutorService что-то выполняется. Так и было задумано?
Мне кажется, самая главная полезность — это осознание, что gradle — это не только система сборки, но и язык программирования (groovy), который в процессе билда дает возможность создавать и рушить вселенные, ну или еще что-нибудь полезное. У нас, к примеру, генерируется файл с историей версий (релизов) на основе гит тэгов.
Кусочек скрипта
def String[] tags = formatTagNames(getTagList())
def AppHistory appHistory = getAppHistory(tags)
def String jsonHistory = new GsonBuilder().setPrettyPrinting().create().toJson(appHistory)
new File("app_history.json").withWriter{ it << jsonHistory }

Тестировали дебажную версию. Заметили, что версия дебажная. Знали, что дебажная версия работает медленно. Зарелизили дебажную верисю.
Яндекс, тычо??
А еще всё ломается, если ввести туда () { 0; };
Прелесть rsync в том, что алгоритм на определяющей стороне (в нашем случае — девайс) весьма быстрый и позволяет определить блоки только за 1 проход по файлу, при чем даже не загружая его в память целиком. Почему Вы считаете, что это жрет батарею?
Еще нужно проверять каждую из загруженных статей на её актуальность: её могут исправить или удалить. Старые статьи при этом детектируются в тот же момент и не нужно их удалять через «какое-то» время. Определить diff для всех статей — это всего 1 http-запрос, получить сам diff — второй. Не нужно заботиться о консистентности данных внутри статьи и между ними. На мой взгляд, нет не проще :)

Information

Rating
Does not participate
Location
Новосибирск, Новосибирская обл., Россия
Date of birth
Registered
Activity