Подход, который вы описали, применяем в части проектов. Но не все проекты хотят завязываться на data binding от Google (по разным причинам). Вы правы, что это упростит привязку состояния к определенным элементам отображения. Но к сожалению, есть вещи, ради которых имеет смысл встраиваться в жизненный цикл родительского компонента. Классический пример (есть и другие) — компонент получения местоположения пользователя: пока activity/fragment в состоянии STARTED, нужно подписываться на датчики; если это делать при создании view, то скажется на расходе батареи.
Хочу подчеркнуть, что рассматриваю подход, разобранный в статье, как лишь один из инструментов в арсенале разработчика. Если он избыточен в большинстве случаев, то применять его не стоит. Но есть экраны, где он поможет структурировать код и сделать более читаемым. Поэтому и решил поделиться с сообществом)
Если вы про data binding, который предоставляет Google из коробки, то, насколько я знаю, он не позволяет настроить запуск действий в определенные моменты жизненного цикла (а такое иногда требуется, хоть и не очень часто). Поправьте, если не прав.
Если вы про data binding как концепцию в целом, то тут все зависит от реализации — возможно, где-то по умолчанию есть такая возможность. Буду благодарен за пример.
Добрый день. Спасибо за статью!
Есть небольшие замечания по коду, напрямую не относящиеся к содержанию статьи.
В методе mapToNewsViewData(news: List<News>) на каждой итерации создается объект Regex и DateFormat, хотя по сути они для всех одинаковы. В дополнение к этому заполнение итогового списка выглядело бы лучше (читабильнее) при использовании stream подхода. Например, так:
class NewsMapper {
companion object {
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale("ru"))
private val regex = "\\.".toRegex()
}
fun mapToNewsViewData(news: List<News>): List<NewsViewData> {
return news.asSequence()
.map {
val textSplits = it.text.split(regex)
NewsViewData(
id = it.date.toString(),
title = textSplits[0],
description = textSplits[1].trim(),
date = dateFormat.format(it.date)
)
}
.toList()
}
}
Спасибо за статью. На мой взгляд, один из важных момент при работе с Rx — помнить про подписки и не забывать отписываться от них. В приведенных примерах, как-то про это забыли. У Вас в данном случае создается бесконечный Observable, подписка на который нигде не сохраняется, но от которого нужно отписаться в onDestroy(). Хотелось бы, чтобы пользователи, которые будут читать статью, видели «картину в целом» и следовали хорошим практикам.
Спасибо за статью. В Вашем примере Observable для SearchView будет не совсем корректно работать. Вы на него подписываетесь в onCreate(), но при срабатывании onQueryTextSubmit у Вас произойдет отписка, так как вызовется onComplete. Получается, что повторный поиск не будет работать. Чтобы повторный поиск работал, нужно избавиться от subject.onComplete();
Дело тут вовсе не в контексте. Давайте на простом примере. В приложении создан класс, ответственный за общение с сервером. Как правило, чтобы не создавать несколько инстансов такого класса используют либо синглтон, либо создают его в Application (руками или с помощью компонента даггера) и соответственно добираются к инстансу этого класса через Application. В примере выше как раз описывался примерно такой случай.
Согласен, что ссылку, сохраненную в onAttach(), нужно чистить в onDetach(), чтобы не было утечки памяти. И в реальном коде это было. Я не стал это копировать сюда, так как это напрямую не относилось к теме статьи.
«Каноническое» означает основанное на вере, что так должно быть. Но разве разработка не подразумевает поиск новых подходов?! Я лишь поделился подходом, а применять его или нет — на усмотрение каждого конкретного разработчика. В любом случае, спасибо за конструктивную критику.
ИМХО, EventBus — не панацея. Я хотел сделать компонент как можно более независимым от разных библиотек, чтобы его можно было переиспользовать в других проектах (где этих библиотек может не быть и добавление их не одобрят).
К тому же, хоть EventBus добавляет гибкости во взаимодействии между компонентами, он также накладывает больше ответственности на разработчиков — чтобы приложение не превратилось в запутанный клубок из событий. Лично я предпочитаю не злоупотреблять рассылкой событий и использовать EventBus только при острой необходимости.
Давайте по порядку. Начнем с сериализации: согласен, что при таком подходе появляются накладные расходы на сериализацию, но, на мой взгляд, сохранение и восстановление одного объекта (не обладающего внутренним состоянием) не приведет к падению производительности. Можно вместо Serializable использовать Pacrelable, если Вы хотите более быстрой сериализации. К тому же, если Android SDK предоставляет возможность сохранять в Bundle сериализуемые объекты, то почему бы нам этим не воспользоваться, а не прикрываться фразами типа «медленная сериализация».
Теперь по поводу «самописного DI». Если Вы внимательно читали статью, то я нигде не призывал отказываться от DI фреймворков, а наоборот предлагаю использовать подход, описанный в публикации, совместно c ними. Давайте возьмем для примера Dagger до появления функционала AndroidInjection, приходилось писать подобное (код с официального сайта):
public class FrombulationActivity extends Activity {
@Inject Frombulator frombulator;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// DO THIS FIRST. Otherwise frombulator might be null!
((SomeApplicationBaseType) getContext().getApplicationContext())
.getApplicationComponent()
.newActivityComponentBuilder()
.activity(this)
.build()
.inject(this);
// ... now you can write the exciting code
}
}
Я лишь предлагал спрятать создание активити компонента. Примерно так.
interface Provider {
Builder getBuilder(FrombulationActivity activity);
}
class ProviderImpl implements Provider {
@Override
Builder getActivityComponent(FrombulationActivity activity) {
return ((SomeApplicationBaseType) getContext().getApplicationContext())
.getApplicationComponent()
.newActivityComponentBuilder()
.activity(activity)
.build();
}
}
public class FrombulationActivity extends Activity {
@Inject Frombulator frombulator;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// DO THIS FIRST. Otherwise frombulator might be null!
Provider provider = getIntent().getExtras().getSerializable(PROVIDER);
provider.getActivityComponent(this)
.inject(this);
// ... now you can write the exciting code
}
}
При таком подходе активити не знает откуда берется компонент. Если активити лежит в общей библиотеке, которую использую несколько приложений, то такой способ внедрения зависимостей очень удобен. А также при тестировании активити.
Подход, который вы описали, применяем в части проектов. Но не все проекты хотят завязываться на data binding от Google (по разным причинам). Вы правы, что это упростит привязку состояния к определенным элементам отображения. Но к сожалению, есть вещи, ради которых имеет смысл встраиваться в жизненный цикл родительского компонента. Классический пример (есть и другие) — компонент получения местоположения пользователя: пока activity/fragment в состоянии STARTED, нужно подписываться на датчики; если это делать при создании view, то скажется на расходе батареи.
Хочу подчеркнуть, что рассматриваю подход, разобранный в статье, как лишь один из инструментов в арсенале разработчика. Если он избыточен в большинстве случаев, то применять его не стоит. Но есть экраны, где он поможет структурировать код и сделать более читаемым. Поэтому и решил поделиться с сообществом)
Если вы про data binding, который предоставляет Google из коробки, то, насколько я знаю, он не позволяет настроить запуск действий в определенные моменты жизненного цикла (а такое иногда требуется, хоть и не очень часто). Поправьте, если не прав.
Если вы про data binding как концепцию в целом, то тут все зависит от реализации — возможно, где-то по умолчанию есть такая возможность. Буду благодарен за пример.
В статье указаны даты проведения
но на сайте конференции заявлены 7-8 декабря 2019. Подскажите, пожалуйста, какие же даты правильные?
Добрый день. Спасибо за статью!
Есть небольшие замечания по коду, напрямую не относящиеся к содержанию статьи.
В методе
mapToNewsViewData(news: List<News>)
на каждой итерации создается объектRegex
иDateFormat
, хотя по сути они для всех одинаковы. В дополнение к этому заполнение итогового списка выглядело бы лучше (читабильнее) при использовании stream подхода. Например, так:Rx
— помнить про подписки и не забывать отписываться от них. В приведенных примерах, как-то про это забыли. У Вас в данном случае создается бесконечныйObservable
, подписка на который нигде не сохраняется, но от которого нужно отписаться вonDestroy()
. Хотелось бы, чтобы пользователи, которые будут читать статью, видели «картину в целом» и следовали хорошим практикам.Observable
дляSearchView
будет не совсем корректно работать. Вы на него подписываетесь вonCreate()
, но при срабатыванииonQueryTextSubmit
у Вас произойдет отписка, так как вызоветсяonComplete
. Получается, что повторный поиск не будет работать. Чтобы повторный поиск работал, нужно избавиться отsubject.onComplete();
onAttach()
, нужно чистить вonDetach()
, чтобы не было утечки памяти. И в реальном коде это было. Я не стал это копировать сюда, так как это напрямую не относилось к теме статьи.К тому же, хоть EventBus добавляет гибкости во взаимодействии между компонентами, он также накладывает больше ответственности на разработчиков — чтобы приложение не превратилось в запутанный клубок из событий. Лично я предпочитаю не злоупотреблять рассылкой событий и использовать EventBus только при острой необходимости.
Serializable
использоватьPacrelable
, если Вы хотите более быстрой сериализации. К тому же, если Android SDK предоставляет возможность сохранять в Bundle сериализуемые объекты, то почему бы нам этим не воспользоваться, а не прикрываться фразами типа «медленная сериализация».Теперь по поводу «самописного DI». Если Вы внимательно читали статью, то я нигде не призывал отказываться от DI фреймворков, а наоборот предлагаю использовать подход, описанный в публикации, совместно c ними. Давайте возьмем для примера Dagger до появления функционала
AndroidInjection
, приходилось писать подобное (код с официального сайта):Я лишь предлагал спрятать создание активити компонента. Примерно так.
При таком подходе активити не знает откуда берется компонент. Если активити лежит в общей библиотеке, которую использую несколько приложений, то такой способ внедрения зависимостей очень удобен. А также при тестировании активити.