通过观察 ViewModel 在 RecyclerView 中搜索 PagedList 的 LiveData

Searching a LiveData of PagedList in RecyclerView by Observing ViewModel

有了 android 分页库,从数据库中分块加载数据真的很容易,ViewModel 提供自动 UI 更新和数据生存。所有这些框架模块都帮助我们在 android 平台上创建出色的应用程序。

典型的 android 应用程序必须显示项目列表并允许用户搜索该列表。这就是我想通过我的应用程序实现的目标。所以我通过阅读许多文档,教程甚至Whosebug答案来完成实现。 但我不太确定我做的是否正确或我应该如何做。下面,我展示了我使用 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< PagedList< Event>>[ 进行阅读和搜索=34=] 所有事件或根据搜索文本筛选的事件。 每次 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;
}

我强烈建议您开始使用 RxJava,它可以简化查找搜索逻辑的整个问题。

我推荐道馆Class你实现两种方法,一种是在搜索为空时查询所有数据,另一种是查询搜索到的项目,如下所示。数据源用于加载页面列表中的数据

 @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 Class 中,我们需要两个参数,一个用于观察搜索到的文本,一个用于在 OnChange 期间通知视图的 MutableLiveData。然后 LiveData 观察 Items 列表并更新 UI。 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();
            }
        });
    }

然后 viewModel 会将 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 并使用搜索到的项目更新 UI。

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

希望有所帮助

感谢George Machibya for his great 。但我更喜欢对它做一些修改,如下所示:

  1. 在将 none 过滤后的数据保留在内存中以使其更快或每次加载它们以优化内存之间需要权衡。我更喜欢将它们保存在内存中,所以我更改了部分代码如下:
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();
            }
        });
  1. 拥有去抖器有助于减少对数据库的查询次数并提高性能。所以我开发了 DebouncedLiveData class 如下所示,并从 filterFoodName.
  2. 制作了一个去抖动的实时数据
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 -> {
...
});
  1. 我通常更喜欢在 android 中使用 DataBiding。通过使用双向数据绑定,您不再需要使用 TextWatcher,您可以直接将 TextView 绑定到 viewModel。

顺便说一句,我修改了 George Machibya 解决方案并将其推送到我的 Github 中。更多详情可以看here.