Pull to refresh
11
0

Lead Developer

Send message
Статья полезная, а из моего комментария реально полезен пожалуй только 3 пункт.
Но спасибо :)
По 1 замечанию, еще с год назад у меня так не выходило написать, странно. Сейчас попробовал и всё сработало как часы, спасибо.
По 2 пункту — о каких регионах речь?
Код картинками — что-то новенькое :)

Ниже замечания по делу:
1. Для строковых ресурсов так, как вы написали, не напишешь.
Вот правильный пример:
<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>

2. Plurals не всегда корректно работают с локалями.
3. Для облегчения перевода одинаковые строки можно объединить в одну, а остальные ссылать на нее:
<string name="my_string">111</string>
<string name="your_string">@string/my_string</string>

4. Чтобы проще было разделять ресурсы по блокам, не обязательно использовать конструкцию, как у вас
<!-- Работает -->
// Тоже работает!
<string name="...">...</string>
Из коробки нету такого поведения.
Долго искал грамотное решение, так как считаю rebase опасным в неопытных руках.
Я откатываюсь на коммит, в котором рабочая ветка была создана, делаю новую ветку для мержа с мастером в этом месте и пишу
git merge --squash 1234567
С этой командой все изменения можно будет как один коммит добавить.
Но у вас в тулбаре нету пояснения за иконки :)
И мы говорим про FAB в рамках андроида — на вашем скрине фейсбука этого не видно. FAB Material
Конечно верю вам, что многие люди не понимают (казалось бы) очевидных вещей.
Иначе WhatsApp и Facebook не добавляли бы комментарии на FAB элементы, как вот тут:
Я бы показывал эту надпись до первого клика на кнопке, имхо UX бы улучшило.
Тут
image
Являясь левшой и обладателем телефона Elephone P8000, под споилер приложу картинку, показывающую максимальную границу, до которой дотянется мой большой палец. Как видно, даже правша до тулбара не дотягивается.
Картинка
image
Спасибо за ответ.
Жирный шрифт легко можно заменить на звездочку/квадратик/кружочек перед текстом и текст будет целостнее.
Почему нету в варианте справа слова «Зарплата: 200-220...»?
Ведь «Занятость: полная занятость» — такая же тавтология.

По кнопке «откликнуться» — при первом открытии можно показать, что эта кнопка значит.
Иконку всегда легче текста запомнить. Любой видеоредактор на этом принципе построен — все кнопки на своих местах.
Мне вот непонятен зачеркнутый глаз вверху — меняем на «ОТСЛЕЖИВАТЬ»? )
Имхо, исходный вариант выглядит лучше всего. В toolbar добавить текст «Вакансия» вместо «Просмотр вакансии», убрать «Вас пригласили» и всё. Странно, что результат вы сравниваете на разных вакансиях — на исходной часть текста не влезла, а конечная по высоте длиннее и непонятно что можно скроллить, а что нет — как так?

Надеюсь, мой отзыв будет полезен.
Попытаюсь обосновать, почему исходный вариант лучше + Сравнение 2 картинок
image
Нет смысла писать «Занятость: Полная занятость, Полный день». Вариант слева очевиднее.
По этой же логике странно, что в варианте справа нету текста "Зарплата: 200-220..."
Нет смысла выделять текст жирным, это сложнее читать, так как это не заголовки.
Круглая кнопка очевидна, другой функции, кроме как «Откликнуться» от нее ожидать смысла нету.
Это много лучше, чем перекрытие текста большой кнопкой.
Текст слева удобнее читать — он просто крупнее и более темный. Светло-серый на белом читать тяжеловато.
Слева иконка метро относится к адресу, что логично (рядом с метро), а не к карте.
Реально лучше стал выглядеть только toolbar.
Интересная статья.
Жаль, что была проигнорирована сообществом.
Пожалуй, единственное пожелание — используйте в примерах свои собственные объекты ThreadPoolExecutor, так как названия вроде «RxNewThreadScheduler-1» тяжело читаются. Было бы легче читать имена «ComputationThread», «IoThread» и т.д.
посмотрел последние коммиты — немного изменился класс

CursorUiTransformer
//composer
class CursorUiTransformer implements Observable.Transformer<Cursor, Cursor> {

  private Cursor cursor;

  @Override
  public Observable<Cursor> call(Observable<Cursor> observable) {
    return observable
      .subscribeOn(RxExecutors.io())
      .observeOn(RxExecutors.ui())
      .unsubscribeOn(RxExecutors.ui())
      .doOnNext(cursor -> {
        closeCursor();
        this.cursor = cursor;
      })
      .doOnUnsubscribe(this::closeCursor);
  }

  private void closeCursor() {
    CursorUtil.close(this.cursor);
  }
}

//usage example
cursorObservable
...
.compose(new CursorAutoCloser()) // apply at the end


Так делается для того, чтобы старый Cursor закрывался при множественных onNext (обновления данных) и в ui потоке — в таком случае нету конфликтов с адаптером.
Идея в том, что объекты, которые используют данный List не должны знать о том, что по факту это Cursor.

В большинстве мест в коде используется вот такая конструкция:
Ссылка для понимания, что именно я использую — LINK

Собственно код:
//composer
class CursorAutoCloser implements Observable.Transformer<Cursor, Cursor> {

  private Cursor cursor;

  @Override
  public Observable<Cursor> call(Observable<Cursor> observable) {
    return observable
      .map(cursor -> this.cursor = cursor)
      .doOnUnsubscribe(() -> CursorUtil.close(this.cursor));
  }
}

//part of CursorUtil
class CursorUtil {
  ...
  public static void close(Cursor cursor) {
    if (cursor != null && !cursor.isClosed()) {
      cursor.close();
    }
  }
  ...
}

//usage example
cursorObservable
.compose(new CursorAutoCloser())
...
При большом количестве входных данных есть большой шанс словить OutOfMemory, используя ArrayList, так как все данные хранятся в памяти.
Cursor лишен этого недостатка, так как подгрузка с носителя данных таблицы в память происходит по мере надобности, как и выгрузка.
Сейчас удобно использовать MVP и непонятно, что мешало ввести кэширование на уровне слоя данных.
Уровень View просто не должен знать о кэшировании.
Также непонятна причина неиспользования базы данных для хранения результатов запроса.
Получили данные, записали результат в базу, вернули List (который враппер над Cursor).
При повороте или восстановлении данных с экрана смотрим, есть ли закэшированный запрос.

У вас же много ненужного и непонятного кода, про code-style и naming я вообще молчу.
Есть сообщество андроид-разработчиков, которое использует общепринятые стандарты, которые тут игнорируются, потому что «я так привык». Это неуважение.

Извините, но код уровня junior developer, статью я бы советовал только примером — «как усложнить себе жизнь и делать не нужно».

Вот вам некая абстракция, которая куда привлекательнее, уровень View — внутри Fragment:

model
  .compose(bindToLifecycle()) // rxLifecycle
  .getRecords()
  .subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe(
    records -> {
      // TODO show result here
    },
    throwable -> {
      // TODO show error here
    });


И вот вам абстракция, уровень Model:

class Model {
  ...

  private BehaviorSubject<List<Record>> recordsSubject = BehaviorSubject.create();

  public Observable<List<Record>> getRecords() {
    return Observable.<List<Record>>create(subscriber -> {
      // some synchronization here - begin
      if (cacheIsEmpty()) {
        List<Record> records = loadRecordsFromWeb();
        saveInDatabase(records);
      }
      // some synchronization here - end

      List<Record> cachedRecords = getListFromDatabase();

      subscriber.onNext(cachedRecords);
      subscriber.onCompleted();
    })
    .flatMap(records -> {
      recordsSubject.onNext(records);
      return recordsSubject.asObservable();
    });
  }

  ...
}


Бонусом класс — wrapper над объектом Cursor (надеюсь объяснение, почему лучше использовать курсор вместо ArrayList для большого количества входных данных давать не нужно):
DbList
public class DbList<T> extends AbstractList<T> {

  public interface Factory<T> {
    T get(Cursor cursor);
  }

  public static <T> List<T> create(Cursor cursor, Factory<T> factory) {
    return new DbList<>(cursor, factory);
  }

  private final Cursor cursor;
  private final Factory<T> factory;

  private DbList(Cursor cursor, Factory<T> factory) {
    this.cursor = cursor;
    this.factory = factory;
  }

  @Override
  @Nullable
  public T get(int location) {
    if (cursor != null && cursor.moveToPosition(location)) {
      return factory.get(cursor);
    }
    return null;
  }

  @Override
  public int size() {
    return cursor == null ? 0 : cursor.getCount();
  }

}


Есть познавательная статья по расчету освещения для 2D-объектов, может будет интересна сообществу: LINK

Визуализация алгоритма:
image
Так 5 стрингов в памяти там всегда будут.
Вы наверное не поняли, почему лучше использовать стрингбилдер.
Даже AndroidStudio предложит стрингбилдер заменить на сложение — под капотом всё будет оптимизировано в таких случаях.

String result = "1" + "2" + "3";
// под капотом без оптимизаций (но тут всегда оптимизация будет) превращается в
String temp = "1" + "2";
String result = temp + "3";

В примере создается промежуточная строка. Если бы был вот такой цикл:

String result = "";
for (int i=0; i< 100; ++i){
    // тут проблема, так как оптимизировать работу со строками компилятор не сможет
    // постоянно создаются промежуточные строки и будет бессмысленная нагрузка на GC
    // и лучше заменить на StringBuilder, но в нем будет 100 элементов
    result = result + i;
}

Конкретно по вашему вопросу — строка создалась, строка удалилась после вызова GC — экономия на спичках, не то место для оптимизаций.
А чем плох вариант с ограничением размера кэша? Скажем 30 записей в кэше максимум, более старые удаляются при добавлении новых. Ну или 5 мегабайт, вариантов много.
Нечто похожее можно было наблюдать для проекта «Казаки». Можно почитать ТУТ.
Были некоторые проблемы и в графическом департаменте. Огромные объемы двумерной графики, которыми приходилось оперировать, привели нас к созданию собственного формата: каждый кадр паковался отдельно, используя алгоритм LZ с общим словарем. За счет этого достигалась хорошая степень сжатия, а доступ к каждому кадру не требовал распаковки предыдущих.
С проблемой потери кадров я не сталкивался, но описал способ, при котором можно писать в любой файл с любым перекрытием.
Камера пишет в стрим. То, что он обертка над 10 другими стримами никого не волнует. Она не выключается ине переподключается при создании нового файла, в этом нету смысла.
А для чего нужно перекрытие? Ни 1 кадра не теряется, смысло мало в этом имхо.

По вопросу записи сразу в 2 файла — да, почти из коробки.
Пишете вы в OutputStream, и по факту это может быть обертка под другие File или Memory OutputStream в любом количестве.
Извините, но невозможно читать данный перевод. Исправьте и сделайте текст более дружелюбным для русскоговорящих людей.

Information

Rating
Does not participate
Registered
Activity