Комментарии 36
Мы пойдем по более простому пути...
А жаль, про то как организовывать работу с RxJava было бы интересно почитать. Причем, интересует именно вопрос хранения данных при повороте экрана. В loader'ах об это думает система, а в RxAndroid обычно советуют cache() + какое-нибудь статическое хранилище для обсерверов. А как вы храните их?)
Я допускаю, что и об архитектуре с Rx могу написать, но не обещаю :)
Но на самом деле, мы в легионе используем для Rx похожую модель. Такое же использование лоадеров, чтобы система заботилась + всякие фишки и операторы от Rx, довольно неплохо получается. Вот только мне она не очень нравится (сильно больше классов выходит), а ничего получше я пока не придумал. Вот если появятся хорошие идеи, то можно и статью написать.
Но на самом деле, мы в легионе используем для Rx похожую модель. Такое же использование лоадеров, чтобы система заботилась + всякие фишки и операторы от Rx, довольно неплохо получается. Вот только мне она не очень нравится (сильно больше классов выходит), а ничего получше я пока не придумал. Вот если появятся хорошие идеи, то можно и статью написать.
В последнее время обкатываю связку Rx + Retrofit-like транспорт слой (возвращает Observable) + AndroidViewModel (MVVM библиотека с VM, переживающей повороты и пр., на похожей идее основана Chronos от Роботов). Пока полёт нормальный и таких простыней нет.
Речь идет об архитектуре, а в статье нет ни одной диаграммы
Я так и не понял, зачем нужно каждый ответ писать в базу только для того чтоб сразу из нее прочитать, а при ошибке вернуть null.
В лоадере мы только загружаем и сохраняем данные. Если они нам требуются дальше, то, разумеется, мы не ходим каждый раз на сервер, а берем из базы. Например, мы можем на сплеше один раз загрузить все данные в лоадерах и сохранить их в базу (при этом можно вообще всегда возвращать null — это будет лишь означать то, что загрузка данного лоадера завершена, и можно загружать следующий — при последовательных запросах), а после работать только локально.
Если кратко — то лоадеры отдают нам данные один раз и долго, а из базы мы можем их вытащить всегда и сразу.
Если кратко — то лоадеры отдают нам данные один раз и долго, а из базы мы можем их вытащить всегда и сразу.
Это понятно, но имхо время жизни объектов, равно как и правила их кеширования должны задаваться в HTTP, и, следовательно, обрабатываться на том же уровне. Если ответ сервера еще не протух, то сетевая библиотека сразу же должна возвратить закешированный результат. Конечно, в некоторых случаях оправдано хранить модель данных локально, если есть жесткие требования к работе оффлайн, но это далеко не самый обычный http клиент.
Я склонен считать, что всегда есть смысл хранить данные локально. Что будет, если у вас сервер вдруг отвалится? Никакое кэширование в HTTP уже не поможет. И приложение несколько даже не будет открываться.
Все равно не понимаю вашу нелюбовь к локальной базе :) Занимает не так много места, а дает возможность постоянно быстро получать данные из разных мест без всяких запросов и прочем. Не вижу недостатков в таком подходе, а преимущества, как мне кажется, очевидны.
Все равно не понимаю вашу нелюбовь к локальной базе :) Занимает не так много места, а дает возможность постоянно быстро получать данные из разных мест без всяких запросов и прочем. Не вижу недостатков в таком подходе, а преимущества, как мне кажется, очевидны.
Нет, никакой нелюбви, просто в приведенной схеме БД только замедляет сетевой стек. А если сервер вдруг отвалится, то все закешированные ответы будут лежать в кеше и отдаваться мгновенно без участия БД.
Честно скажу, что не работал с кэшированием в http, так что дальше могу ошибаться. Насколько я понимаю, кэширование тоже выполняется локально в файлах или как-то еще. Скорость, конечно, повыше чем из бд, но не настолько, чтобы это было явным преимуществом. Но если мы говорим о структурированных данных разного типа из разных запросов, то без бд тут не обойтись. Да и когда у нас в приложении 50 разных запросов, использовать кэш для всех тоже вряд ли разумно. Кроме того, может случиться так, что кэш очистится (если закрыл приложение и не использовал его какое-то время), запустил приложение, а сервер лежит. С бд не попадем в такую ситуацию.
Впрочем да, я с вам согласен частично, в каких-то приложениях достаточно будет такого кэширование, но все же такая архитектура менее расширяема, и при росте приложения ее будет сложно поддерживать.
Впрочем да, я с вам согласен частично, в каких-то приложениях достаточно будет такого кэширование, но все же такая архитектура менее расширяема, и при росте приложения ее будет сложно поддерживать.
Потому что а) источник данных должен быть один б) активити может быть прибита в любой момент.
Поэтому в ситуации создание активити — запрос — прибитие активити такой подход позволяет все-таки сохранить данные, и на следующем запуске их показать.
Плюс, от запроса не всегда приходят просто данные для показа. Часто это данные бизнес-логики(данные для авторизации н-р).
Поэтому в ситуации создание активити — запрос — прибитие активити такой подход позволяет все-таки сохранить данные, и на следующем запуске их показать.
Плюс, от запроса не всегда приходят просто данные для показа. Часто это данные бизнес-логики(данные для авторизации н-р).
Я могу ошибаться, но в статье вроде об этом не сказано и не приведен код который так может работать, так что идет нарушение принципа yagni.
Не знаю, правильно ли вас понял, но пример для б) я приводил в самом начале, с запросом в Activity. Там запрос выполнится, но при преждевременной смерти Activity, результат просто потеряется.
По сути база данных нужна для нескольких кейсов:
1) Даже если мы всегда ходим за данными на бэкенд, то все равно в один прекрасный момент интернет может пропасть, и нам придется работать локально.
2) Мы работаем с какой-то сложной структурой данных, и нужно использовать данные от предыдущих запросов (даже из апи примера в статье, получили список аэропортов, потом нужно получить список популярных направлений с учетом этих аэропортов [может, бред говорю, не изучал это апи]. Как их хранить? Напрямую в памяти как-то не очень хорошо. Кэширование запроса тоже не подходит. Вот и используем БД.)
По сути база данных нужна для нескольких кейсов:
1) Даже если мы всегда ходим за данными на бэкенд, то все равно в один прекрасный момент интернет может пропасть, и нам придется работать локально.
2) Мы работаем с какой-то сложной структурой данных, и нужно использовать данные от предыдущих запросов (даже из апи примера в статье, получили список аэропортов, потом нужно получить список популярных направлений с учетом этих аэропортов [может, бред говорю, не изучал это апи]. Как их хранить? Напрямую в памяти как-то не очень хорошо. Кэширование запроса тоже не подходит. Вот и используем БД.)
Хранить напрямую в памяти часто это очень хорошо, т.к. очень быстро, а данных обычно не десятки мегабайт. Если процесс прибивается, то все равно нужно все перезагружать путем дерганья сетевых api, не так ли? А где кешировать полученные данные — в сетевой библиотеке или в БД это архитектурный вопрос, который зависит от того, необходима ли гарантированная работа в оффлайн режиме с синхронизацией изменений в каком-то другом сервисе. Если синхронизация происходит прозрачно, то и дергать сеть ручками не нужно, а если в ручную, то и БД особо не нужна наверное. Все зависит от типа приложения, конечно.
В общем, да, я с вами согласен. БДшки на самом деле потихоньку могут становится архаизмом в данных случаях, учитывая постоянно растущую мощность устройств, сейчас уже не страшно хранить все в памяти или в кэше.
Из плюсов все-таки можно считать, что бд обеспечивает большую стабильность.
Из плюсов все-таки можно считать, что бд обеспечивает большую стабильность.
Ой, ну ладно. Прям уж архаизм? Имхо, гораздо удобнее хранить сложные структуры в БД, нежели в Map<String, Map<String, Map<String… >>>>>>>>>> =) К тому же, :memory: хранилище в БД никто не отменял. Тот же SQLite прекрасно может в память!
Зачем же такие страсти с мапами? Ничего удобнее и быстрее чем доступ к POJO нет.
Мы вообще переходим на такой подход, что в БД храним чистый json, который приходит с сервера (то есть один столбец строковый в бд), а при создании объекта получаем эту строку и через Gson конвертируем. Получается вот такая немного странная сериализация. Намного удобнее, на самом деле. В этом случае нельзя сказать, что БД нам для чего-то необходима.
P.S. Упс, не совсем в ту ветку добавил коммент)
P.S. Упс, не совсем в ту ветку добавил коммент)
Тоже подход, но это уже крайний случай, если уж совсем никак. Хотя, скоро будет праздник для любителей хранить JSON: www.sqlite.org/src/timeline?r=json
Да, без сомнения POJO лучше, но сути особо не меняет, особенно, если нужны операции фильтрации, сортировки и прочие прелести (а они нужны в большинстве случаев).
Вопрос вне данной архитектуры, но раз уж вы используете realm.io, то как вы сообщаете UI об изменениях в базе? Eventbus?
Всем хорош realm.io, но этот вопрос не дает мне покоя.
Про RX тоже очень интересно было бы почитать.
Всем хорош realm.io, но этот вопрос не дает мне покоя.
Про RX тоже очень интересно было бы почитать.
Да, с реалмом вопрос хороший, и в принципе любой bus с этим может справиться, хотя они не добавляют плюсов к карме в архитектуру. Обычно в сочетании с Realm мы используем Rx, и уже соответственно, средства Rx для таких оповещений.
Я не то чтобы сильный фанат реалма на самом деле, его использовал лишь для примера, что можно легко перейти к другой БД.
Хорошо, я всерьез подумаю о том, чтобы в ближайший месяц максимально разобраться с Rx и что-то такое написать :) Хотя не исключено, что это сделает еще кто-нибудь :)
Я не то чтобы сильный фанат реалма на самом деле, его использовал лишь для примера, что можно легко перейти к другой БД.
Хорошо, я всерьез подумаю о том, чтобы в ближайший месяц максимально разобраться с Rx и что-то такое написать :) Хотя не исключено, что это сделает еще кто-нибудь :)
А что вы используете в качестве БД в своих проектах?
Голый SQLite это ад. Всякие annotation фрэимворки упрощают конечно это дело, но с реалмом ни в какое сравнение не идут.
Голый SQLite это ад. Всякие annotation фрэимворки упрощают конечно это дело, но с реалмом ни в какое сравнение не идут.
Спасибо за развернутый обзор!
Подскажите пожалуйста, при использовании Rx, как решаете проблему кеширования и оффлайн работы? Например отображать сначала сохраненные данные, проверить необходимость обновления, запустить загрузку новых данных и отобразить.
Подскажите пожалуйста, при использовании Rx, как решаете проблему кеширования и оффлайн работы? Например отображать сначала сохраненные данные, проверить необходимость обновления, запустить загрузку новых данных и отобразить.
Например отображать сначала сохраненные данные, проверить необходимость обновления, запустить загрузку новых данных и отобразить.
Такой подход можно использовать независимо от того, что вы используете для получения данных с бэкенда. Здесь важно лишь то, является ли критичным показ актуальных данных, или можно сначала показать старые. У нас общая политика обычно такая — всегда сначала ходим на бэкенд, а уже потом достаем из кэша, в случае ошибки. Обычно стараемся подгружать данные за экран-два до того, как они понадобятся, так что такой подход тоже неплох.
Схема с Rx примерно такая (могу немного ошибаться, так как с Rx-проектами не работал особо) — получаем Observable, потом в flatMap сохраняем данные (или повторяем запрос, если ошибка) и прокидываем Observable в UI.
запускать AsyncTask-и и сильно бить в бубен
Я последний раз работал с Android API 4.0.2, но бубнов не помю.
Раньше в Android единственным доступным средством для выполнения сетевых запросов был клиент Apache, который на самом деле далек от идеала, и не зря сейчас Google усиленно старается избавиться от него в новых приложениях.
HTTP Apache Client — прекрасная библиотека, используемая в куче Java проектов. В энтерпрайзе полно.
Позже плодом стараний разработчиков Google стал класс HttpUrlConnection.
Разве HttpUrlConnection не часть Java API? (JavaDoc).
Он ситуацию исправил не сильно. По-прежнему не хватало возможности выполнять асинхронные запросы, хотя модель HttpUrlConnection + Loaders уже является более-менее работоспособной.
А как связана многопоточность(асинхронность) и работа с сетью (HttpUrlConnection)?
А Retrofit библиотека хорошая.
Я последний раз работал с Android API 4.0.2, но бубнов не помю.
AsyncTask-и никак не связаны с жизненным циклом Activity / Fragment. Со всеми вытекающими проблемами при уничтожении Activity / закрытии приложения.
HTTP Apache Client — прекрасная библиотека, используемая в куче Java проектов. В энтерпрайзе полно.
Библиотека отличная, без сомнений. Проблема в том, что в Android SDK включена не сама библиотека как зависимость, а ее самая начальная beta-версия. Сейчас в API 23 те, кто хочет продолжать использовать Apache, уже подключают саму библиотеку.
Разве HttpUrlConnection не часть Java API? (JavaDoc).
Да, здесь я погрешил против истины. В свое оправдание могу сказать, что Google все равно переработал этот класс.
А как связана многопоточность(асинхронность) и работа с сетью (HttpUrlConnection)?
Если рассматривать их отдельно, то никак. А если в контексте разработки приложения, то связь самая прямая. Не очень-то возможно работать с сетью без асинхронности / многопоточности.
compile 'com.squareup.retrofit:retrofit:2.0.0-beta1'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta1'
compile 'com.squareup.okhttp:okhttp:2.0.0'
public class Airport {
@SerializedName("iata")
private String mIata;
@SerializedName("name")
private String mName;
@SerializedName("airport_name")
private String mAirportName;
public Airport() {
}
}
public interface AirportsService {
@GET("/places/coords_to_places_ru.json")
Call<List<Airport>> airports(@Query("coords") String gps);
}
public class ApiFactory {
private static final int CONNECT_TIMEOUT = 15;
private static final int WRITE_TIMEOUT = 60;
private static final int TIMEOUT = 60;
private static final OkHttpClient CLIENT = new OkHttpClient();
static {
CLIENT.setConnectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS);
CLIENT.setWriteTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS);
CLIENT.setReadTimeout(TIMEOUT, TimeUnit.SECONDS);
}
@NonNull
public static AirportsService getAirportsService() {
return getRetrofit().create(AirportsService.class);
}
@NonNull
private static Retrofit getRetrofit() {
return new Retrofit.Builder()
.baseUrl(BuildConfig.API_ENDPOINT)
.addConverterFactory(GsonConverterFactory.create())
.client(CLIENT)
.build();
}
}
public class MainActivity extends AppCompatActivity implements Callback<List<Airport>> {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AirportsService service = ApiFactory.getAirportsService();
Call<List<Airport>> call = service.airports("55.749792,37.6324949");
call.enqueue(this);
}
@Override
public void onResponse(Response<List<Airport>> response) {
if (response.isSuccess()) {
List<Airport> airports = response.body();
//do something here
}
}
@Override
public void onFailure(Throwable t) {
}
}
> Все кажется очень простым.
То есть получение списка объектов вида «три строки» HTTP-запросом в три экрана кода — это просто и хорошо, что, наконец, появились удобные библиотеки???
Так, для сравнения, на некоторых языках этот код выглядит вот так:
fetch('/places/coords_to_places_ru.json').then(
function (response) {
return response.json();
},
console.error.bind(console)
);
Осталось распарсить json и преобразовать в объект
response.json() именно это и делает.
Вот запрос.
MainActivity — это UI часть.
ApiFactory — общий класс для всех запросов. Больше он не трогается.
Зависимости считать кодом тоже как-то странно.
Можете как-нибудь сравнить это с кодом, который писался раньше плюс ручной парсинг json-а.
@GET("/places/coords_to_places_ru.json")
Call<List<Airport>> airports(@Query("coords") String gps);
MainActivity — это UI часть.
ApiFactory — общий класс для всех запросов. Больше он не трогается.
Зависимости считать кодом тоже как-то странно.
Можете как-нибудь сравнить это с кодом, который писался раньше плюс ручной парсинг json-а.
Мало того, решение можно сделать ещё более «простым», подпилив gson, чтоб не нужно было писать аннотации в модели =) Gson умеет «сам» убирать префикс m и переводить CamelCase к lower_case_with_underscores и обратно(когда переводим объект в json):
new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.setFieldNamingStrategy(new FieldNamingStrategy {
public String translateName(Field field) {
String name = FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES.translateName(field);
name = name.substring(2, name.length()).toLowerCase();
return name;
}
})
.create();
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Android архитектура клиент-серверного приложения