Подход, который вы описали, применяем в части проектов. Но не все проекты хотят завязываться на 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, приходилось писать подобное (код с официального сайта):Я лишь предлагал спрятать создание активити компонента. Примерно так.
При таком подходе активити не знает откуда берется компонент. Если активити лежит в общей библиотеке, которую использую несколько приложений, то такой способ внедрения зависимостей очень удобен. А также при тестировании активити.