Pull to refresh
9
0.1
Сергей Винярский @SergeyVin

Android-разработчик

Send message

Тут точно не знаю. Полагаю, что попросит позвонить в call-центр

Потому что встроенный антивирус проверяет СМС на попытки социнжиниринга. Вынужденная мера, учитывая величину клиентской базы. Но, впрочем, в этом механизме грядут изменения к лучшему.

Версия, по-моему, Delphi 2007

TIntegerList тогда ещё точно не было. Про остальное не помню. Помню боль, связанную с этим, а детали выветрились. Ещё помню, очень активно использовали TClientDataSet, но за него отдельно платить надо было.

Вы не правы. Я работал когда-то с ОЧЕНЬ большим проектом на Delphi. Думаю, не сильно ошибусь, если скажу, что там было порядка 20 млн. строк кода. И никаких особых проблем не было. Главное — разделить код на логические слои, которые, в свою очередь, раскидать по отдельным dll. Причем каждая dll — это отдельный COM-объект, то есть он загружается в память только если пользователь обратился к соответствующему куску функциональности. Проблема не в Delphi, а в очень низком пороге входа в него, что приводило к говно-софту с бизнес-логикой в OnClick. Вот в чем реально проблема Delphi, это отсутствие библиотеки коллекций. StringList и все. По крайней мере так было в те далёкие времена, когда я работал с Delphi.

Упс. Извиняюсь. Неправильно слово применил: респондент — человек, принимающий участие в опросе.

Вот, кстати, соглашусь. С одной стороны смешно, с другой — не очень. Эти люди могут быть необразованы, писать с ошибками, но за этими письмами скрыты реальные проблемы реальных людей. Я бы не стал их публиковать. Вы же не спросили разрешения на публикацию у респондентов?

А теперь добавим в эту солянку default-методы в интерфейсах в Java 8. Вот уж костыль из костылей...

Способ с таймерами будет глючить, если в процессе отображения сплеша менять ориентацию экрана. Активити будет пересоздаваться и запускать новый таймер, да еще и без остановки старого. А если отменять предыдущий postDelayed, мы получим бесконечный сплеш.
Выходит, что надо задать в манифесте android:screenOrientation. Но тогда, понятное дело, ухудшается пользовательский опыт, т.к. при запуске приложения вдруг ориентация будет сама меняться.

В PlaybackState есть и длина трека и текущая позиция. Посмотрите на то, как это реализовано в UniversalMusicPlayer. UniversalMusicPlayer — это пример от Google. Там реализовано то же самое, что и в статье. Просто UniversalMusicPlayer очень сложен для понимания с нуля, поэтому я и написал упрощенный вариант.
В UI метод updateProgress дергается с некоторой периодичностью. Метод забирает из PlaybackState последнюю известную позицию и время, когда State был обновлен, и высчитывает дельту. Соответственно в сервисе по каждому "чиху" state с позицией обновляется (в методе updatePlaybackState).
Если позицию писать в PlaybackState, то она будет везде, а если самому пробрасывать данные, то позиция будет только в вашей Activity.

LiveData не имеет встроенных механизмов обработки ошибок. Придется делать что-то типа LiveData<Data>, где


class Data {
  Status status;
  Object payLoad; // Полезная нагрузка, ради которой все затевается
  String errorMessage;
}

enum Status { SUCCESS, LOADING, FAIL }

и передавать руками статусы и сообщения об ошибках вместе с данными. Соответственно, создалась Activity, подписалась на LiveData в своей ViewModel и сразу получила закешированные (или пустые данные) + статус. Если статус LOADING, включается progress, иначе выключается.
Такой подход рекомендует Google на примере обработки состояния сетевого запроса: Addendum: exposing network status


Любопытно, кстати, что если мы вызовем несколько раз liveData.setValue с одинаковым значением, обзерверы так же будут вызваны несколько раз с одним и тем же значением.


Проблему с поворотом Activity более менее я понял как LiveData решает. А что с убийством Activity при сворачивании приложения?

Внутри фреймворка хранение моделей реализовано через специальные retained fragments. Соответственно, если Activity будет убита системой, ViewModel и все LiveData будут также убиты. И весь процесс начнется сначала, как будто приложение запускают впервые.

Не совсем корректно написал: конечно же активити у модели может быть только одна, следовательно, и Lifecycle единственный, а вот если мы модель привязываем к нескольким фрагментам, то каждый из них вызовет addLifecycle(this.getLifecycle()) и мы получим в модели список из нескольких Lifecycle.

Но ведь помимо Lazy есть другие способы загрузить свойство навигации (в терминах EF).

Google говорит так:


However, on the client side, lazy loading is not feasible because it's likely to happen on the UI thread, and querying information on disk in the UI thread creates significant performance problems.

А, если загружать сразу?


If you don't use lazy loading, however, the app fetches more data than it needs, creating memory consumption problems.

Поэтому


For these reasons, Room disallows object references between entity classes. Instead, you must explicitly request the data that your app needs.

Addendum: No object references between entities

Предполагается, что все данные — во ViewModel. И если какие-то из них — LiveData, то ViewModel подписана на них, а Activity — во ViewModel? Как обрабатывается стейт активити внутри ViewModel?

По сути никак. Событие onCleared — единственная точка влияния стейта активити на ViewModel. Интеллектуальная обработка стейта происходит целиком, полностью и независимо в каждой LiveData, которые модель выставляет наружу. Если вам в модели надо знать состояние активити, надо вручную сделать метод addLifecycle(Lifecycle lifecycle), который каждая активити вызовет как addLifecycle(this.getLifecycle()). А дальше в модели подписываться на события каждого Lifecycle и вручную отрабатывать, что, например, модель должна делать, если два активити стали PAUSED и один ACTIVE.
Я полагаю, что как правило LiveData в модели будут данные брать не из воздуха, а получая их из LiveData в Repository. Это делается через трансформацию (аналог операторов в rxJava):


// В WeatherRepository:

public LiveData<WeatherInfo> getWeather(String cityName) {
  // Данные из БД
}

// В MainActivityViewModel:

private final MutableLiveData<String> cityNameLiveData = new MutableLiveData<>();
private final LiveData<WeatherInfo> weatherInfoLiveData;

public MainActivityViewModel() {
     this.weatherInfoLiveData = Transformations.switchMap(cityNameLiveData, weatherRepository::getWeather);
}

// Вызывает UI, когда хочет получить данные по наименованию города
public void selectCity(String cityName) {
   cityNameLiveData.setValue(cityName);
}

// На это UI подписывается
public LiveData<WeatherInfo> weatherInfo() {
     return weatherInfoLiveData;
}

Здесь у нас есть один инстанс weatherInfoLiveData, в котором живут стейты подписавшихся активити и Transformations.switchMap, который говорит "каждый раз, когда cityNameLiveData пришлет новые данные, вызови weatherRepository.getWeather(cityNameLiveData.getValue) и подпишись на тот LiveData, который он вернет, а все эмитируемые им данные передавай в weatherInfoLiveData".
Если при этом данные надо преобразовать, то можно еще использовать Transformation.map(LiveData source, Function func).

2

Information

Rating
2,233-rd
Location
Жуковский, Москва и Московская обл., Россия
Date of birth
Registered
Activity