Поиск LiveData PagedList в RecyclerView, наблюдая ViewModel - PullRequest
7 голосов
/ 10 января 2020

С android библиотекой подкачки данных действительно легко загружать данные из базы данных кусками, а ViewModel обеспечивает автоматическое обновление c пользовательского интерфейса и выживание данных. Все эти модули фреймворков помогают нам создать отличное приложение на платформе android.

Типичное приложение android должно отображать список элементов и позволяет пользователю выполнять поиск в этом списке. И это то, чего я хочу достичь с помощью моего приложения. Таким образом, я реализовал реализацию, прочитав множество документов, учебных пособий и даже ответов на стекопотоки. Но я не уверен, правильно ли я это делаю или как я должен это делать. Итак, ниже я показал свой способ реализации библиотеки подкачки с ViewModel и RecyclerView.

Пожалуйста , просмотрите мою реализацию и исправьте меня, если я ошибаюсь, или покажите, как я должен это сделать. Я думаю, что многие новые android разработчики, подобные мне, все еще не понимают, как это сделать правильно, поскольку нет единого источника, чтобы получить ответы на все ваши вопросы о такой реализации.

Я показываю только то, что думаю важно показать. Я использую комнату. Вот моя сущность, с которой я работаю.

@Entity(tableName = "event")
public class Event {
    @PrimaryKey(autoGenerate = true)
    public int id;

    public String title;
}

Вот DAO для сущности события.

@Dao
public interface EventDao {
    @Query("SELECT * FROM event WHERE event.title LIKE :searchTerm")
    DataSource.Factory<Integer, Event> getFilteredEvent(String searchTerm);
}

Вот ViewModel extends AndroidViewModel , который позволяет читать и искать, предоставляя LiveData > всех событий или отфильтрованных событий в соответствии с текстом поиска. Я действительно борюсь с идеей, что каждый раз, когда происходит изменение в filterEvent, я создаю новые LiveData, которые могут быть избыточными или плохими.

private MutableLiveData<Event> filterEvent = new MutableLiveData<>();
private LiveData<PagedList<Event>> data;

private MeDB meDB;

public EventViewModel(Application application) {
    super(application);
    meDB = MeDB.getInstance(application);

    data = Transformations.switchMap(filterEvent, new Function<Event, LiveData<PagedList<Event>>>() {
        @Override
        public LiveData<PagedList<Event>> apply(Event event) {
            if (event == null) {
                // get all the events
                return new LivePagedListBuilder<>(meDB.getEventDao().getAllEvent(), 5).build();
            } else {
                // get events that match the title
                return new LivePagedListBuilder<>(meDB.getEventDao()
                          .getFilteredEvent("%" + event.title + "%"), 5).build();
            }
        }
    });
}

public LiveData<PagedList<Event>> getEvent(Event event) {
    filterEvent.setValue(event);
    return data;
}

Для поиска события, Я использую SearchView . В onQueryTextChange я написал следующий код для поиска или отображения всех событий, когда не указаны условия поиска, что означает, что поиск выполнен или отменен.

Event dumpEvent;

@Override
public boolean onQueryTextChange(String newText) {

    if (newText.equals("") || newText.length() == 0) {
        // show all the events
        viewModel.getEvent(null).observe(this, events -> adapter.submitList(events));
    }

    // don't create more than one object of event; reuse it every time this methods gets called
    if (dumpEvent == null) {
        dumpEvent = new Event(newText, "", -1, -1);
    }

    dumpEvent.title = newText;

    // get event that match search terms
    viewModel.getEvent(dumpEvent).observe(this, events -> adapter.submitList(events));

    return true;
}

Ответы [ 2 ]

3 голосов
/ 14 января 2020

Спасибо Джорджу Мачибье за его замечательный ответ . Но я предпочитаю вносить в него некоторые изменения, как показано ниже:

  1. Существует компромисс между хранением не отфильтрованных данных в памяти, чтобы сделать их быстрее, или загрузкой их каждый раз для оптимизации памяти. Я предпочитаю хранить их в памяти, поэтому я изменил часть кода следующим образом:
listAllFood = Transformations.switchMap(filterFoodName), input -> {
            if (input == null || input.equals("") || input.equals("%%")) {
                //check if the current value is empty load all data else search
                synchronized (this) {
                    //check data is loaded before or not
                    if (listAllFoodsInDb == null)
                        listAllFoodsInDb = new LivePagedListBuilder<>(
                                foodDao.loadAllFood(), config)
                                .build();
                }
                return listAllFoodsInDb;
            } else {
                return new LivePagedListBuilder<>(
                        foodDao.loadAllFoodFromSearch("%" + input + "%"), config)
                        .build();
            }
        });
Наличие debouncer помогает сократить количество запросов к базе данных и повышает производительность. Поэтому я разработал DebouncedLiveData класс как ниже и сделал опровергнутые данные от filterFoodName.
public class DebouncedLiveData<T> extends MediatorLiveData<T> {

    private LiveData<T> mSource;
    private int mDuration;
    private Runnable debounceRunnable = new Runnable() {
        @Override
        public void run() {
            DebouncedLiveData.this.postValue(mSource.getValue());
        }
    };
    private Handler handler = new Handler();

    public DebouncedLiveData(LiveData<T> source, int duration) {
        this.mSource = source;
        this.mDuration = duration;

        this.addSource(mSource, new Observer<T>() {
            @Override
            public void onChanged(T t) {
                handler.removeCallbacks(debounceRunnable);
                handler.postDelayed(debounceRunnable, mDuration);
            }
        });
    }
}

А затем использовал его как ниже:

listAllFood = Transformations.switchMap(new DebouncedLiveData<>(filterFoodName, 400), input -> {
...
});
Я обычно предпочитаю использовать DataBiding в android. Используя двухстороннее связывание данных, вам больше не нужно использовать TextWatcher, и вы можете напрямую привязать свой TextView к viewModel.

Кстати, я изменил решение Джорджа Мачибья и нажал это в моем Github. Для более подробной информации вы можете увидеть это здесь .

2 голосов
/ 13 января 2020

Я настоятельно рекомендую вам начать использовать Rx Java, и вы можете упростить задачу поиска в логи поиска c.

Я рекомендую в классе комнат Дао реализовать два метода: один для запроса всех данных при пустом поиске, а другой - для поиска искомого элемента следующим образом. Источник данных используется для загрузки данных в список страниц

 @Query("SELECT * FROM food order by food_name")
 DataSource.Factory<Integer, Food> loadAllFood();

@Query("SELECT * FROM food where food_name LIKE  :name order by food_name")
DataSource.Factory<Integer, Food> loadAllFoodFromSearch(String name);

В классе ViewModel нам нужно два параметра, один из которых будет использоваться для наблюдаемого искомого текста, и что мы используем MutableLiveData, которая будет уведомлять представления во время просмотра OnChange. А затем LiveData для наблюдения за списком предметов и обновления пользовательского интерфейса. SwitchMap применяет функцию, которая принимает входные данные LiveData и генерирует соответствующие выходные данные LiveData. Найдите приведенный ниже код

public LiveData<PagedList<Food>> listAllFood;
public MutableLiveData<String> filterFoodName = new MutableLiveData<>();

public void initialFood(final FoodDao foodDao) {
    this.foodDao = foodDao;

    PagedList.Config config = (new PagedList.Config.Builder())
            .setPageSize(10)
            .build();

    listAllFood = Transformations.switchMap(filterFoodName, outputLive -> {

               if (outputLive == null || outputLive.equals("") || input.equals("%%")) {
                //check if the current value is empty load all data else search
                return new LivePagedListBuilder<>(
                        foodDao.loadAllFood(), config)
                        .build();
            } else {
                   return new LivePagedListBuilder<>(
                        foodDao.loadAllFoodFromSearch(input),config)
                        .build();
            }
        });
    }

. Затем модель представления будет распространять LiveData в представления и наблюдать за изменением данных. Затем в MainActivity мы вызываем метод initialFood, который будет использовать нашу функцию SwitchMap.

  viewModel = ViewModelProviders.of(this).get(FoodViewModel.class);
  viewModel.initialFood(FoodDatabase.getINSTANCE(this).foodDao());

  viewModel.listAllFood.observe(this, foodlistPaging -> {
        try {
     Log.d(LOG_TAG, "list of all page number " + foodlistPaging.size());

            foodsactivity = foodlistPaging;
            adapter.submitList(foodlistPaging);

        } catch (Exception e) {
        }
    });

  recyclerView.setAdapter(adapter);

Для первого onCreate инициируйте filterFoodName со значением Null, чтобы получить все элементы. viewModel.filterFoodName.setValue ("");

Затем примените TextChangeListener к EditText и вызовите MutableLiveData, который будет наблюдать за изменением, и обновите пользовательский интерфейс с найденным элементом.

searchFood.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, 
int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int 
 i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                //just set the current value to search.
                viewModel.filterFoodName.
                        setValue("%" + editable.toString() + "%");
            }
        });
    }

ниже мой github репо с полным кодом.

https://github.com/muchbeer/PagingSearchFood

Надеюсь, что помощь

...